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 PENDING_EXIT_SHARED_ELEMENTS = "android:pendingExitSharedElements";
     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 and will be transferred back during exit animation.
     47      */
     48     private ArrayList<String> mPendingExitNames;
     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 = new SparseArray<>();
    116         }
    117         WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
    118         // clean up old references:
    119         for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
    120             WeakReference<ExitTransitionCoordinator> oldRef
    121                     = mExitTransitionCoordinators.valueAt(i);
    122             if (oldRef.get() == null) {
    123                 mExitTransitionCoordinators.removeAt(i);
    124             }
    125         }
    126         int newKey = mExitTransitionCoordinatorsKey++;
    127         mExitTransitionCoordinators.append(newKey, ref);
    128         return newKey;
    129     }
    130 
    131     public void readState(Bundle bundle) {
    132         if (bundle != null) {
    133             if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
    134                 mPendingExitNames = bundle.getStringArrayList(PENDING_EXIT_SHARED_ELEMENTS);
    135             }
    136             if (mEnterTransitionCoordinator == null) {
    137                 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
    138                 mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
    139             }
    140         }
    141     }
    142 
    143     /**
    144      * Returns the element names to be used for exit animation. It caches the list internally so
    145      * that it is preserved through activty destroy and restore.
    146      */
    147     private ArrayList<String> getPendingExitNames() {
    148         if (mPendingExitNames == null && mEnterTransitionCoordinator != null) {
    149             mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
    150         }
    151         return mPendingExitNames;
    152     }
    153 
    154     public void saveState(Bundle bundle) {
    155         ArrayList<String> pendingExitNames = getPendingExitNames();
    156         if (pendingExitNames != null) {
    157             bundle.putStringArrayList(PENDING_EXIT_SHARED_ELEMENTS, pendingExitNames);
    158         }
    159         if (mExitingFrom != null) {
    160             bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
    161             bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
    162         }
    163     }
    164 
    165     public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
    166         final Window window = activity.getWindow();
    167         if (window == null) {
    168             return;
    169         }
    170         // ensure Decor View has been created so that the window features are activated
    171         window.getDecorView();
    172         if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
    173                 && options != null && mEnterActivityOptions == null
    174                 && mEnterTransitionCoordinator == null
    175                 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
    176             mEnterActivityOptions = options;
    177             mIsEnterTriggered = false;
    178             if (mEnterActivityOptions.isReturning()) {
    179                 restoreExitedViews();
    180                 int result = mEnterActivityOptions.getResultCode();
    181                 if (result != 0) {
    182                     Intent intent = mEnterActivityOptions.getResultData();
    183                     if (intent != null) {
    184                         intent.setExtrasClassLoader(activity.getClassLoader());
    185                     }
    186                     activity.onActivityReenter(result, intent);
    187                 }
    188             }
    189         }
    190     }
    191 
    192     public void enterReady(Activity activity) {
    193         if (mEnterActivityOptions == null || mIsEnterTriggered) {
    194             return;
    195         }
    196         mIsEnterTriggered = true;
    197         mHasExited = false;
    198         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
    199         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
    200         if (mEnterActivityOptions.isReturning()) {
    201             restoreExitedViews();
    202             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
    203         }
    204         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
    205                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
    206                 mEnterActivityOptions.isCrossTask());
    207         if (mEnterActivityOptions.isCrossTask()) {
    208             mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
    209             mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
    210         }
    211 
    212         if (!mIsEnterPostponed) {
    213             startEnter();
    214         }
    215     }
    216 
    217     public void postponeEnterTransition() {
    218         mIsEnterPostponed = true;
    219     }
    220 
    221     public void startPostponedEnterTransition() {
    222         if (mIsEnterPostponed) {
    223             mIsEnterPostponed = false;
    224             if (mEnterTransitionCoordinator != null) {
    225                 startEnter();
    226             }
    227         }
    228     }
    229 
    230     private void startEnter() {
    231         if (mEnterTransitionCoordinator.isReturning()) {
    232             if (mExitingToView != null) {
    233                 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
    234                         mExitingToView);
    235             } else {
    236                 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
    237             }
    238         } else {
    239             mEnterTransitionCoordinator.namedViewsReady(null, null);
    240             mPendingExitNames = null;
    241         }
    242 
    243         mExitingFrom = null;
    244         mExitingTo = null;
    245         mExitingToView = null;
    246         mEnterActivityOptions = null;
    247     }
    248 
    249     public void onStop() {
    250         restoreExitedViews();
    251         if (mEnterTransitionCoordinator != null) {
    252             mEnterTransitionCoordinator.stop();
    253             mEnterTransitionCoordinator = null;
    254         }
    255         if (mReturnExitCoordinator != null) {
    256             mReturnExitCoordinator.stop();
    257             mReturnExitCoordinator = null;
    258         }
    259     }
    260 
    261     public void onResume(Activity activity) {
    262         // After orientation change, the onResume can come in before the top Activity has
    263         // left, so if the Activity is not top, wait a second for the top Activity to exit.
    264         if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
    265             restoreExitedViews();
    266             restoreReenteringViews();
    267         } else {
    268             activity.mHandler.postDelayed(new Runnable() {
    269                 @Override
    270                 public void run() {
    271                     if (mEnterTransitionCoordinator == null ||
    272                             mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
    273                         restoreExitedViews();
    274                         restoreReenteringViews();
    275                     }
    276                 }
    277             }, 1000);
    278         }
    279     }
    280 
    281     public void clear() {
    282         mPendingExitNames = null;
    283         mExitingFrom = null;
    284         mExitingTo = null;
    285         mExitingToView = null;
    286         mCalledExitCoordinator = null;
    287         mEnterTransitionCoordinator = null;
    288         mEnterActivityOptions = null;
    289         mExitTransitionCoordinators = null;
    290     }
    291 
    292     private void restoreExitedViews() {
    293         if (mCalledExitCoordinator != null) {
    294             mCalledExitCoordinator.resetViews();
    295             mCalledExitCoordinator = null;
    296         }
    297     }
    298 
    299     private void restoreReenteringViews() {
    300         if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
    301                 !mEnterTransitionCoordinator.isCrossTask()) {
    302             mEnterTransitionCoordinator.forceViewsToAppear();
    303             mExitingFrom = null;
    304             mExitingTo = null;
    305             mExitingToView = null;
    306         }
    307     }
    308 
    309     public boolean startExitBackTransition(final Activity activity) {
    310         ArrayList<String> pendingExitNames = getPendingExitNames();
    311         if (pendingExitNames == null || mCalledExitCoordinator != null) {
    312             return false;
    313         } else {
    314             if (!mHasExited) {
    315                 mHasExited = true;
    316                 Transition enterViewsTransition = null;
    317                 ViewGroup decor = null;
    318                 boolean delayExitBack = false;
    319                 if (mEnterTransitionCoordinator != null) {
    320                     enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
    321                     decor = mEnterTransitionCoordinator.getDecor();
    322                     delayExitBack = mEnterTransitionCoordinator.cancelEnter();
    323                     mEnterTransitionCoordinator = null;
    324                     if (enterViewsTransition != null && decor != null) {
    325                         enterViewsTransition.pause(decor);
    326                     }
    327                 }
    328 
    329                 mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
    330                         activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
    331                         null, null, true);
    332                 if (enterViewsTransition != null && decor != null) {
    333                     enterViewsTransition.resume(decor);
    334                 }
    335                 if (delayExitBack && decor != null) {
    336                     final ViewGroup finalDecor = decor;
    337                     OneShotPreDrawListener.add(decor, () -> {
    338                         if (mReturnExitCoordinator != null) {
    339                             mReturnExitCoordinator.startExit(activity.mResultCode,
    340                                     activity.mResultData);
    341                         }
    342                     });
    343                 } else {
    344                     mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
    345                 }
    346             }
    347             return true;
    348         }
    349     }
    350 
    351     public boolean isTransitionRunning() {
    352         // Note that *only* enter *or* exit will be running at any given time
    353         if (mEnterTransitionCoordinator != null) {
    354             if (mEnterTransitionCoordinator.isTransitionRunning()) {
    355                 return true;
    356             }
    357         }
    358         if (mCalledExitCoordinator != null) {
    359             if (mCalledExitCoordinator.isTransitionRunning()) {
    360                 return true;
    361             }
    362         }
    363         if (mReturnExitCoordinator != null) {
    364             if (mReturnExitCoordinator.isTransitionRunning()) {
    365                 return true;
    366             }
    367         }
    368         return false;
    369     }
    370 
    371     public void startExitOutTransition(Activity activity, Bundle options) {
    372         mEnterTransitionCoordinator = null;
    373         if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
    374                 mExitTransitionCoordinators == null) {
    375             return;
    376         }
    377         ActivityOptions activityOptions = new ActivityOptions(options);
    378         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
    379             int key = activityOptions.getExitCoordinatorKey();
    380             int index = mExitTransitionCoordinators.indexOfKey(key);
    381             if (index >= 0) {
    382                 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
    383                 mExitTransitionCoordinators.removeAt(index);
    384                 if (mCalledExitCoordinator != null) {
    385                     mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
    386                     mExitingTo = mCalledExitCoordinator.getMappedNames();
    387                     mExitingToView = mCalledExitCoordinator.copyMappedViews();
    388                     mCalledExitCoordinator.startExit();
    389                 }
    390             }
    391         }
    392     }
    393 }
    394