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.content.Intent;
     19 import android.os.Bundle;
     20 import android.os.ResultReceiver;
     21 import android.transition.Transition;
     22 import android.util.SparseArray;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 import android.view.Window;
     26 
     27 import com.android.internal.view.OneShotPreDrawListener;
     28 
     29 import java.lang.ref.WeakReference;
     30 import java.util.ArrayList;
     31 
     32 /**
     33  * This class contains all persistence-related functionality for Activity Transitions.
     34  * Activities start exit and enter Activity Transitions through this class.
     35  */
     36 class ActivityTransitionState {
     37 
     38     private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
     39 
     40     private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
     41 
     42     private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
     43 
     44     /**
     45      * The shared elements that the calling Activity has said that they transferred to this
     46      * Activity.
     47      */
     48     private ArrayList<String> mEnteringNames;
     49 
     50     /**
     51      * The names of shared elements that were shared to the called Activity.
     52      */
     53     private ArrayList<String> mExitingFrom;
     54 
     55     /**
     56      * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
     57      */
     58     private ArrayList<String> mExitingTo;
     59 
     60     /**
     61      * The local Views that were shared out, mapped to those elements in mExitingFrom.
     62      */
     63     private ArrayList<View> mExitingToView;
     64 
     65     /**
     66      * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
     67      * Visibility of exited Views.
     68      */
     69     private ExitTransitionCoordinator mCalledExitCoordinator;
     70 
     71     /**
     72      * The ExitTransitionCoordinator used to return to a previous Activity when called with
     73      * {@link android.app.Activity#finishAfterTransition()}.
     74      */
     75     private ExitTransitionCoordinator mReturnExitCoordinator;
     76 
     77     /**
     78      * We must be able to cancel entering transitions to stop changing the Window to
     79      * opaque when we exit before making the Window opaque.
     80      */
     81     private EnterTransitionCoordinator mEnterTransitionCoordinator;
     82 
     83     /**
     84      * ActivityOptions used on entering this Activity.
     85      */
     86     private ActivityOptions mEnterActivityOptions;
     87 
     88     /**
     89      * Has an exit transition been started? If so, we don't want to double-exit.
     90      */
     91     private boolean mHasExited;
     92 
     93     /**
     94      * Postpone painting and starting the enter transition until this is false.
     95      */
     96     private boolean mIsEnterPostponed;
     97 
     98     /**
     99      * Potential exit transition coordinators.
    100      */
    101     private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
    102 
    103     /**
    104      * Next key for mExitTransitionCoordinator.
    105      */
    106     private int mExitTransitionCoordinatorsKey = 1;
    107 
    108     private boolean mIsEnterTriggered;
    109 
    110     public ActivityTransitionState() {
    111     }
    112 
    113     public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
    114         if (mExitTransitionCoordinators == null) {
    115             mExitTransitionCoordinators =
    116                     new SparseArray<WeakReference<ExitTransitionCoordinator>>();
    117         }
    118         WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
    119         // clean up old references:
    120         for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
    121             WeakReference<ExitTransitionCoordinator> oldRef
    122                     = mExitTransitionCoordinators.valueAt(i);
    123             if (oldRef.get() == null) {
    124                 mExitTransitionCoordinators.removeAt(i);
    125             }
    126         }
    127         int newKey = mExitTransitionCoordinatorsKey++;
    128         mExitTransitionCoordinators.append(newKey, ref);
    129         return newKey;
    130     }
    131 
    132     public void readState(Bundle bundle) {
    133         if (bundle != null) {
    134             if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
    135                 mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
    136             }
    137             if (mEnterTransitionCoordinator == null) {
    138                 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
    139                 mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
    140             }
    141         }
    142     }
    143 
    144     public void saveState(Bundle bundle) {
    145         if (mEnteringNames != null) {
    146             bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
    147         }
    148         if (mExitingFrom != null) {
    149             bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
    150             bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
    151         }
    152     }
    153 
    154     public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
    155         final Window window = activity.getWindow();
    156         if (window == null) {
    157             return;
    158         }
    159         // ensure Decor View has been created so that the window features are activated
    160         window.getDecorView();
    161         if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
    162                 && options != null && mEnterActivityOptions == null
    163                 && mEnterTransitionCoordinator == null
    164                 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
    165             mEnterActivityOptions = options;
    166             mIsEnterTriggered = false;
    167             if (mEnterActivityOptions.isReturning()) {
    168                 restoreExitedViews();
    169                 int result = mEnterActivityOptions.getResultCode();
    170                 if (result != 0) {
    171                     Intent intent = mEnterActivityOptions.getResultData();
    172                     if (intent != null) {
    173                         intent.setExtrasClassLoader(activity.getClassLoader());
    174                     }
    175                     activity.onActivityReenter(result, intent);
    176                 }
    177             }
    178         }
    179     }
    180 
    181     public void enterReady(Activity activity) {
    182         if (mEnterActivityOptions == null || mIsEnterTriggered) {
    183             return;
    184         }
    185         mIsEnterTriggered = true;
    186         mHasExited = false;
    187         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
    188         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
    189         if (mEnterActivityOptions.isReturning()) {
    190             restoreExitedViews();
    191             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
    192         }
    193         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
    194                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
    195                 mEnterActivityOptions.isCrossTask());
    196         if (mEnterActivityOptions.isCrossTask()) {
    197             mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
    198             mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
    199         }
    200 
    201         if (!mIsEnterPostponed) {
    202             startEnter();
    203         }
    204     }
    205 
    206     public void postponeEnterTransition() {
    207         mIsEnterPostponed = true;
    208     }
    209 
    210     public void startPostponedEnterTransition() {
    211         if (mIsEnterPostponed) {
    212             mIsEnterPostponed = false;
    213             if (mEnterTransitionCoordinator != null) {
    214                 startEnter();
    215             }
    216         }
    217     }
    218 
    219     private void startEnter() {
    220         if (mEnterTransitionCoordinator.isReturning()) {
    221             if (mExitingToView != null) {
    222                 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
    223                         mExitingToView);
    224             } else {
    225                 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
    226             }
    227         } else {
    228             mEnterTransitionCoordinator.namedViewsReady(null, null);
    229             mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
    230         }
    231 
    232         mExitingFrom = null;
    233         mExitingTo = null;
    234         mExitingToView = null;
    235         mEnterActivityOptions = null;
    236     }
    237 
    238     public void onStop() {
    239         restoreExitedViews();
    240         if (mEnterTransitionCoordinator != null) {
    241             mEnterTransitionCoordinator.stop();
    242             mEnterTransitionCoordinator = null;
    243         }
    244         if (mReturnExitCoordinator != null) {
    245             mReturnExitCoordinator.stop();
    246             mReturnExitCoordinator = null;
    247         }
    248     }
    249 
    250     public void onResume(Activity activity, boolean isTopOfTask) {
    251         // After orientation change, the onResume can come in before the top Activity has
    252         // left, so if the Activity is not top, wait a second for the top Activity to exit.
    253         if (isTopOfTask || mEnterTransitionCoordinator == null) {
    254             restoreExitedViews();
    255             restoreReenteringViews();
    256         } else {
    257             activity.mHandler.postDelayed(new Runnable() {
    258                 @Override
    259                 public void run() {
    260                     if (mEnterTransitionCoordinator == null ||
    261                             mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
    262                         restoreExitedViews();
    263                         restoreReenteringViews();
    264                     }
    265                 }
    266             }, 1000);
    267         }
    268     }
    269 
    270     public void clear() {
    271         mEnteringNames = null;
    272         mExitingFrom = null;
    273         mExitingTo = null;
    274         mExitingToView = null;
    275         mCalledExitCoordinator = null;
    276         mEnterTransitionCoordinator = null;
    277         mEnterActivityOptions = null;
    278         mExitTransitionCoordinators = null;
    279     }
    280 
    281     private void restoreExitedViews() {
    282         if (mCalledExitCoordinator != null) {
    283             mCalledExitCoordinator.resetViews();
    284             mCalledExitCoordinator = null;
    285         }
    286     }
    287 
    288     private void restoreReenteringViews() {
    289         if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
    290                 !mEnterTransitionCoordinator.isCrossTask()) {
    291             mEnterTransitionCoordinator.forceViewsToAppear();
    292             mExitingFrom = null;
    293             mExitingTo = null;
    294             mExitingToView = null;
    295         }
    296     }
    297 
    298     public boolean startExitBackTransition(final Activity activity) {
    299         if (mEnteringNames == null || mCalledExitCoordinator != null) {
    300             return false;
    301         } else {
    302             if (!mHasExited) {
    303                 mHasExited = true;
    304                 Transition enterViewsTransition = null;
    305                 ViewGroup decor = null;
    306                 boolean delayExitBack = false;
    307                 if (mEnterTransitionCoordinator != null) {
    308                     enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
    309                     decor = mEnterTransitionCoordinator.getDecor();
    310                     delayExitBack = mEnterTransitionCoordinator.cancelEnter();
    311                     mEnterTransitionCoordinator = null;
    312                     if (enterViewsTransition != null && decor != null) {
    313                         enterViewsTransition.pause(decor);
    314                     }
    315                 }
    316 
    317                 mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
    318                         activity.getWindow(), activity.mEnterTransitionListener, mEnteringNames,
    319                         null, null, true);
    320                 if (enterViewsTransition != null && decor != null) {
    321                     enterViewsTransition.resume(decor);
    322                 }
    323                 if (delayExitBack && decor != null) {
    324                     final ViewGroup finalDecor = decor;
    325                     OneShotPreDrawListener.add(decor, () -> {
    326                         if (mReturnExitCoordinator != null) {
    327                             mReturnExitCoordinator.startExit(activity.mResultCode,
    328                                     activity.mResultData);
    329                         }
    330                     });
    331                 } else {
    332                     mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
    333                 }
    334             }
    335             return true;
    336         }
    337     }
    338 
    339     public boolean isTransitionRunning() {
    340         // Note that *only* enter *or* exit will be running at any given time
    341         if (mEnterTransitionCoordinator != null) {
    342             if (mEnterTransitionCoordinator.isTransitionRunning()) {
    343                 return true;
    344             }
    345         }
    346         if (mCalledExitCoordinator != null) {
    347             if (mCalledExitCoordinator.isTransitionRunning()) {
    348                 return true;
    349             }
    350         }
    351         if (mReturnExitCoordinator != null) {
    352             if (mReturnExitCoordinator.isTransitionRunning()) {
    353                 return true;
    354             }
    355         }
    356         return false;
    357     }
    358 
    359     public void startExitOutTransition(Activity activity, Bundle options) {
    360         mEnterTransitionCoordinator = null;
    361         if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
    362                 mExitTransitionCoordinators == null) {
    363             return;
    364         }
    365         ActivityOptions activityOptions = new ActivityOptions(options);
    366         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
    367             int key = activityOptions.getExitCoordinatorKey();
    368             int index = mExitTransitionCoordinators.indexOfKey(key);
    369             if (index >= 0) {
    370                 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
    371                 mExitTransitionCoordinators.removeAt(index);
    372                 if (mCalledExitCoordinator != null) {
    373                     mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
    374                     mExitingTo = mCalledExitCoordinator.getMappedNames();
    375                     mExitingToView = mCalledExitCoordinator.copyMappedViews();
    376                     mCalledExitCoordinator.startExit();
    377                 }
    378             }
    379         }
    380     }
    381 }
    382