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