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 
ActivityTransitionState()110     public ActivityTransitionState() {
111     }
112 
addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator)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 
readState(Bundle bundle)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 
saveState(Bundle bundle)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 
setEnterActivityOptions(Activity activity, ActivityOptions options)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 
enterReady(Activity activity)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 
postponeEnterTransition()206     public void postponeEnterTransition() {
207         mIsEnterPostponed = true;
208     }
209 
startPostponedEnterTransition()210     public void startPostponedEnterTransition() {
211         if (mIsEnterPostponed) {
212             mIsEnterPostponed = false;
213             if (mEnterTransitionCoordinator != null) {
214                 startEnter();
215             }
216         }
217     }
218 
startEnter()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 
onStop()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 
onResume(Activity activity, boolean isTopOfTask)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 
clear()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 
restoreExitedViews()281     private void restoreExitedViews() {
282         if (mCalledExitCoordinator != null) {
283             mCalledExitCoordinator.resetViews();
284             mCalledExitCoordinator = null;
285         }
286     }
287 
restoreReenteringViews()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 
startExitBackTransition(final Activity activity)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 
isTransitionRunning()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 
startExitOutTransition(Activity activity, Bundle options)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