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.graphics.Matrix;
     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.Window;
     34 
     35 import java.util.ArrayList;
     36 
     37 /**
     38  * This ActivityTransitionCoordinator is created by the Activity to manage
     39  * the enter scene and shared element transfer into the Scene, either during
     40  * launch of an Activity or returning from a launched Activity.
     41  */
     42 class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
     43     private static final String TAG = "EnterTransitionCoordinator";
     44 
     45     private static final int MIN_ANIMATION_FRAMES = 2;
     46 
     47     private boolean mSharedElementTransitionStarted;
     48     private Activity mActivity;
     49     private boolean mHasStopped;
     50     private boolean mIsCanceled;
     51     private ObjectAnimator mBackgroundAnimator;
     52     private boolean mIsExitTransitionComplete;
     53     private boolean mIsReadyForTransition;
     54     private Bundle mSharedElementsBundle;
     55     private boolean mWasOpaque;
     56     private boolean mAreViewsReady;
     57     private boolean mIsViewsTransitionStarted;
     58     private boolean mIsViewsTransitionComplete;
     59     private boolean mIsSharedElementTransitionComplete;
     60     private ArrayList<Matrix> mSharedElementParentMatrices;
     61     private Transition mEnterViewsTransition;
     62 
     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 
     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 
    106     public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
    107         triggerViewsReady(mapNamedElements(accepted, localNames));
    108     }
    109 
    110     public Transition getEnterViewsTransition() {
    111         return mEnterViewsTransition;
    112     }
    113 
    114     @Override
    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             setSharedElementMatrices();
    126             moveSharedElementsToOverlay();
    127         }
    128         if (mSharedElementsBundle != null) {
    129             onTakeSharedElements();
    130         }
    131     }
    132 
    133     private void triggerViewsReady(final ArrayMap<String, View> sharedElements) {
    134         if (mAreViewsReady) {
    135             return;
    136         }
    137         mAreViewsReady = true;
    138         // Ensure the views have been laid out before capturing the views -- we need the epicenter.
    139         if (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()) {
    140             viewsReady(sharedElements);
    141         } else {
    142             final View sharedElement = sharedElements.valueAt(0);
    143             sharedElement.getViewTreeObserver()
    144                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    145                 @Override
    146                 public boolean onPreDraw() {
    147                     sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
    148                     viewsReady(sharedElements);
    149                     return true;
    150                 }
    151             });
    152         }
    153     }
    154 
    155     private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
    156             ArrayList<String> localNames) {
    157         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
    158         ViewGroup decorView = getDecor();
    159         if (decorView != null) {
    160             decorView.findNamedViews(sharedElements);
    161         }
    162         if (accepted != null) {
    163             for (int i = 0; i < localNames.size(); i++) {
    164                 String localName = localNames.get(i);
    165                 String acceptedName = accepted.get(i);
    166                 if (localName != null && !localName.equals(acceptedName)) {
    167                     View view = sharedElements.remove(localName);
    168                     if (view != null) {
    169                         sharedElements.put(acceptedName, view);
    170                     }
    171                 }
    172             }
    173         }
    174         return sharedElements;
    175     }
    176 
    177     private void sendSharedElementDestination() {
    178         boolean allReady;
    179         final View decorView = getDecor();
    180         if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
    181             allReady = false;
    182         } else if (decorView == null) {
    183             allReady = true;
    184         } else {
    185             allReady = !decorView.isLayoutRequested();
    186             if (allReady) {
    187                 for (int i = 0; i < mSharedElements.size(); i++) {
    188                     if (mSharedElements.get(i).isLayoutRequested()) {
    189                         allReady = false;
    190                         break;
    191                     }
    192                 }
    193             }
    194         }
    195         if (allReady) {
    196             Bundle state = captureSharedElementState();
    197             setSharedElementMatrices();
    198             moveSharedElementsToOverlay();
    199             mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
    200         } else if (decorView != null) {
    201             decorView.getViewTreeObserver()
    202                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    203                         @Override
    204                         public boolean onPreDraw() {
    205                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
    206                             if (mResultReceiver != null) {
    207                                 Bundle state = captureSharedElementState();
    208                                 setSharedElementMatrices();
    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 
    221     private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
    222         return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
    223     }
    224 
    225     @Override
    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 
    248     private void cancel() {
    249         if (!mIsCanceled) {
    250             mIsCanceled = true;
    251             if (getViewsTransition() == null || mIsViewsTransitionStarted) {
    252                 showViews(mSharedElements, true);
    253             } else if (mTransitioningViews != null) {
    254                 mTransitioningViews.addAll(mSharedElements);
    255             }
    256             mSharedElementNames.clear();
    257             mSharedElements.clear();
    258             mAllSharedElementNames.clear();
    259             startSharedElementTransition(null);
    260             onRemoteExitTransitionComplete();
    261         }
    262     }
    263 
    264     public boolean isReturning() {
    265         return mIsReturning;
    266     }
    267 
    268     protected void prepareEnter() {
    269         ViewGroup decorView = getDecor();
    270         if (mActivity == null || decorView == null) {
    271             return;
    272         }
    273         mActivity.overridePendingTransition(0, 0);
    274         if (!mIsReturning) {
    275             mWasOpaque = mActivity.convertToTranslucent(null, null);
    276             Drawable background = decorView.getBackground();
    277             if (background != null) {
    278                 getWindow().setBackgroundDrawable(null);
    279                 background = background.mutate();
    280                 background.setAlpha(0);
    281                 getWindow().setBackgroundDrawable(background);
    282             }
    283         } else {
    284             mActivity = null; // all done with it now.
    285         }
    286     }
    287 
    288     @Override
    289     protected Transition getViewsTransition() {
    290         Window window = getWindow();
    291         if (window == null) {
    292             return null;
    293         }
    294         if (mIsReturning) {
    295             return window.getReenterTransition();
    296         } else {
    297             return window.getEnterTransition();
    298         }
    299     }
    300 
    301     protected Transition getSharedElementTransition() {
    302         Window window = getWindow();
    303         if (window == null) {
    304             return null;
    305         }
    306         if (mIsReturning) {
    307             return window.getSharedElementReenterTransition();
    308         } else {
    309             return window.getSharedElementEnterTransition();
    310         }
    311     }
    312 
    313     private void startSharedElementTransition(Bundle sharedElementState) {
    314         ViewGroup decorView = getDecor();
    315         if (decorView == null) {
    316             return;
    317         }
    318         // Remove rejected shared elements
    319         ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
    320         rejectedNames.removeAll(mSharedElementNames);
    321         ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
    322         if (mListener != null) {
    323             mListener.onRejectSharedElements(rejectedSnapshots);
    324         }
    325         startRejectedAnimations(rejectedSnapshots);
    326 
    327         // Now start shared element transition
    328         ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
    329                 mSharedElementNames);
    330         showViews(mSharedElements, true);
    331         scheduleSetSharedElementEnd(sharedElementSnapshots);
    332         ArrayList<SharedElementOriginalState> originalImageViewState =
    333                 setSharedElementState(sharedElementState, sharedElementSnapshots);
    334         requestLayoutForSharedElements();
    335 
    336         boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
    337         boolean startSharedElementTransition = true;
    338         setGhostVisibility(View.INVISIBLE);
    339         scheduleGhostVisibilityChange(View.INVISIBLE);
    340         Transition transition = beginTransition(decorView, startEnterTransition,
    341                 startSharedElementTransition);
    342         scheduleGhostVisibilityChange(View.VISIBLE);
    343         setGhostVisibility(View.VISIBLE);
    344 
    345         if (startEnterTransition) {
    346             startEnterTransition(transition);
    347         }
    348 
    349         setOriginalSharedElementState(mSharedElements, originalImageViewState);
    350 
    351         if (mResultReceiver != null) {
    352             // We can't trust that the view will disappear on the same frame that the shared
    353             // element appears here. Assure that we get at least 2 frames for double-buffering.
    354             decorView.postOnAnimation(new Runnable() {
    355                 int mAnimations;
    356 
    357                 @Override
    358                 public void run() {
    359                     if (mAnimations++ < MIN_ANIMATION_FRAMES) {
    360                         View decorView = getDecor();
    361                         if (decorView != null) {
    362                             decorView.postOnAnimation(this);
    363                         }
    364                     } else if (mResultReceiver != null) {
    365                         mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
    366                         mResultReceiver = null; // all done sending messages.
    367                     }
    368                 }
    369             });
    370         }
    371     }
    372 
    373     private void onTakeSharedElements() {
    374         if (!mIsReadyForTransition || mSharedElementsBundle == null) {
    375             return;
    376         }
    377         final Bundle sharedElementState = mSharedElementsBundle;
    378         mSharedElementsBundle = null;
    379         final View decorView = getDecor();
    380         if (decorView != null) {
    381             decorView.getViewTreeObserver()
    382                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    383                         @Override
    384                         public boolean onPreDraw() {
    385                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
    386                             startTransition(new Runnable() {
    387                                 @Override
    388                                 public void run() {
    389                                     startSharedElementTransition(sharedElementState);
    390                                 }
    391                             });
    392                             return false;
    393                         }
    394                     });
    395             decorView.invalidate();
    396         }
    397     }
    398 
    399     private void requestLayoutForSharedElements() {
    400         int numSharedElements = mSharedElements.size();
    401         for (int i = 0; i < numSharedElements; i++) {
    402             mSharedElements.get(i).requestLayout();
    403         }
    404     }
    405 
    406     private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
    407             boolean startSharedElementTransition) {
    408         Transition sharedElementTransition = null;
    409         if (startSharedElementTransition) {
    410             if (!mSharedElementNames.isEmpty()) {
    411                 sharedElementTransition = configureTransition(getSharedElementTransition(), false);
    412             }
    413             if (sharedElementTransition == null) {
    414                 sharedElementTransitionStarted();
    415                 sharedElementTransitionComplete();
    416             } else {
    417                 sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
    418                     @Override
    419                     public void onTransitionStart(Transition transition) {
    420                         sharedElementTransitionStarted();
    421                     }
    422 
    423                     @Override
    424                     public void onTransitionEnd(Transition transition) {
    425                         transition.removeListener(this);
    426                         sharedElementTransitionComplete();
    427                     }
    428                 });
    429             }
    430         }
    431         Transition viewsTransition = null;
    432         if (startEnterTransition) {
    433             mIsViewsTransitionStarted = true;
    434             if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
    435                 viewsTransition = configureTransition(getViewsTransition(), true);
    436                 if (viewsTransition != null && !mIsReturning) {
    437                     stripOffscreenViews();
    438                 }
    439             }
    440             if (viewsTransition == null) {
    441                 viewTransitionComplete();
    442             } else {
    443                 viewsTransition.forceVisibility(View.INVISIBLE, true);
    444                 final ArrayList<View> transitioningViews = mTransitioningViews;
    445                 viewsTransition.addListener(new ContinueTransitionListener() {
    446                     @Override
    447                     public void onTransitionStart(Transition transition) {
    448                         mEnterViewsTransition = transition;
    449                         if (transitioningViews != null) {
    450                             showViews(transitioningViews, false);
    451                         }
    452                         super.onTransitionStart(transition);
    453                     }
    454 
    455                     @Override
    456                     public void onTransitionEnd(Transition transition) {
    457                         mEnterViewsTransition = null;
    458                         transition.removeListener(this);
    459                         viewTransitionComplete();
    460                         super.onTransitionEnd(transition);
    461                     }
    462                 });
    463             }
    464         }
    465 
    466         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
    467         if (transition != null) {
    468             transition.addListener(new ContinueTransitionListener());
    469             TransitionManager.beginDelayedTransition(decorView, transition);
    470             if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
    471                 mSharedElements.get(0).invalidate();
    472             } else if (startEnterTransition && mTransitioningViews != null &&
    473                     !mTransitioningViews.isEmpty()) {
    474                 mTransitioningViews.get(0).invalidate();
    475             }
    476         } else {
    477             transitionStarted();
    478         }
    479         return transition;
    480     }
    481 
    482     private void viewTransitionComplete() {
    483         mIsViewsTransitionComplete = true;
    484         if (mIsSharedElementTransitionComplete) {
    485             moveSharedElementsFromOverlay();
    486         }
    487     }
    488 
    489     private void sharedElementTransitionComplete() {
    490         mIsSharedElementTransitionComplete = true;
    491         if (mIsViewsTransitionComplete) {
    492             moveSharedElementsFromOverlay();
    493         }
    494     }
    495 
    496     private void sharedElementTransitionStarted() {
    497         mSharedElementTransitionStarted = true;
    498         if (mIsExitTransitionComplete) {
    499             send(MSG_EXIT_TRANSITION_COMPLETE, null);
    500         }
    501     }
    502 
    503     private void startEnterTransition(Transition transition) {
    504         ViewGroup decorView = getDecor();
    505         if (!mIsReturning && decorView != null) {
    506             Drawable background = decorView.getBackground();
    507             if (background != null) {
    508                 background = background.mutate();
    509                 getWindow().setBackgroundDrawable(background);
    510                 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
    511                 mBackgroundAnimator.setDuration(getFadeDuration());
    512                 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
    513                     @Override
    514                     public void onAnimationEnd(Animator animation) {
    515                         makeOpaque();
    516                     }
    517                 });
    518                 mBackgroundAnimator.start();
    519             } else if (transition != null) {
    520                 transition.addListener(new Transition.TransitionListenerAdapter() {
    521                     @Override
    522                     public void onTransitionEnd(Transition transition) {
    523                         transition.removeListener(this);
    524                         makeOpaque();
    525                     }
    526                 });
    527             } else {
    528                 makeOpaque();
    529             }
    530         }
    531     }
    532 
    533     public void stop() {
    534         // Restore the background to its previous state since the
    535         // Activity is stopping.
    536         if (mBackgroundAnimator != null) {
    537             mBackgroundAnimator.end();
    538             mBackgroundAnimator = null;
    539         } else if (mWasOpaque) {
    540             ViewGroup decorView = getDecor();
    541             if (decorView != null) {
    542                 Drawable drawable = decorView.getBackground();
    543                 if (drawable != null) {
    544                     drawable.setAlpha(1);
    545                 }
    546             }
    547         }
    548         makeOpaque();
    549         mIsCanceled = true;
    550         mResultReceiver = null;
    551         mActivity = null;
    552         moveSharedElementsFromOverlay();
    553         if (mTransitioningViews != null) {
    554             showViews(mTransitioningViews, true);
    555         }
    556         showViews(mSharedElements, true);
    557         clearState();
    558     }
    559 
    560     public void cancelEnter() {
    561         setGhostVisibility(View.INVISIBLE);
    562         mHasStopped = true;
    563         mIsCanceled = true;
    564         mResultReceiver = null;
    565         if (mBackgroundAnimator != null) {
    566             mBackgroundAnimator.cancel();
    567             mBackgroundAnimator = null;
    568         }
    569         mActivity = null;
    570         clearState();
    571     }
    572 
    573     private void makeOpaque() {
    574         if (!mHasStopped && mActivity != null) {
    575             if (mWasOpaque) {
    576                 mActivity.convertFromTranslucent();
    577             }
    578             mActivity = null;
    579         }
    580     }
    581 
    582     private boolean allowOverlappingTransitions() {
    583         return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
    584                 : getWindow().getAllowEnterTransitionOverlap();
    585     }
    586 
    587     private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
    588         if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
    589             return;
    590         }
    591         final ViewGroup decorView = getDecor();
    592         if (decorView != null) {
    593             ViewGroupOverlay overlay = decorView.getOverlay();
    594             ObjectAnimator animator = null;
    595             int numRejected = rejectedSnapshots.size();
    596             for (int i = 0; i < numRejected; i++) {
    597                 View snapshot = rejectedSnapshots.get(i);
    598                 overlay.add(snapshot);
    599                 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
    600                 animator.start();
    601             }
    602             animator.addListener(new AnimatorListenerAdapter() {
    603                 @Override
    604                 public void onAnimationEnd(Animator animation) {
    605                     ViewGroupOverlay overlay = decorView.getOverlay();
    606                     int numRejected = rejectedSnapshots.size();
    607                     for (int i = 0; i < numRejected; i++) {
    608                         overlay.remove(rejectedSnapshots.get(i));
    609                     }
    610                 }
    611             });
    612         }
    613     }
    614 
    615     protected void onRemoteExitTransitionComplete() {
    616         if (!allowOverlappingTransitions()) {
    617             startEnterTransitionOnly();
    618         }
    619     }
    620 
    621     private void startEnterTransitionOnly() {
    622         startTransition(new Runnable() {
    623             @Override
    624             public void run() {
    625                 boolean startEnterTransition = true;
    626                 boolean startSharedElementTransition = false;
    627                 ViewGroup decorView = getDecor();
    628                 if (decorView != null) {
    629                     Transition transition = beginTransition(decorView, startEnterTransition,
    630                             startSharedElementTransition);
    631                     startEnterTransition(transition);
    632                 }
    633             }
    634         });
    635     }
    636 
    637     private void setSharedElementMatrices() {
    638         int numSharedElements = mSharedElements.size();
    639         if (numSharedElements > 0) {
    640             mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
    641         }
    642         for (int i = 0; i < numSharedElements; i++) {
    643             View view = mSharedElements.get(i);
    644 
    645             // Find the location in the view's parent
    646             ViewGroup parent = (ViewGroup) view.getParent();
    647             Matrix matrix = new Matrix();
    648             parent.transformMatrixToLocal(matrix);
    649 
    650             mSharedElementParentMatrices.add(matrix);
    651         }
    652     }
    653 
    654     @Override
    655     protected void getSharedElementParentMatrix(View view, Matrix matrix) {
    656         int index = mSharedElementParentMatrices == null ? -1 : mSharedElements.indexOf(view);
    657         if (index < 0) {
    658             super.getSharedElementParentMatrix(view, matrix);
    659         } else {
    660             matrix.set(mSharedElementParentMatrices.get(index));
    661         }
    662     }
    663 }
    664