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.get() == 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 && mEnterTransitionCoordinator != null) {
149             mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
150         }
151         return mPendingExitNames;
152     }
153 
saveState(Bundle bundle)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 
setEnterActivityOptions(Activity activity, ActivityOptions options)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 
enterReady(Activity activity)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         final boolean isReturning = mEnterActivityOptions.isReturning();
201         if (isReturning) {
202             restoreExitedViews();
203             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
204         }
205         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
206                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
207                 mEnterActivityOptions.isCrossTask());
208         if (mEnterActivityOptions.isCrossTask()) {
209             mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
210             mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
211         }
212 
213         if (!mIsEnterPostponed) {
214             startEnter();
215         }
216     }
217 
postponeEnterTransition()218     public void postponeEnterTransition() {
219         mIsEnterPostponed = true;
220     }
221 
startPostponedEnterTransition()222     public void startPostponedEnterTransition() {
223         if (mIsEnterPostponed) {
224             mIsEnterPostponed = false;
225             if (mEnterTransitionCoordinator != null) {
226                 startEnter();
227             }
228         }
229     }
230 
startEnter()231     private void startEnter() {
232         if (mEnterTransitionCoordinator.isReturning()) {
233             if (mExitingToView != null) {
234                 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
235                         mExitingToView);
236             } else {
237                 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
238             }
239         } else {
240             mEnterTransitionCoordinator.namedViewsReady(null, null);
241             mPendingExitNames = null;
242         }
243 
244         mExitingFrom = null;
245         mExitingTo = null;
246         mExitingToView = null;
247         mEnterActivityOptions = null;
248     }
249 
onStop()250     public void onStop() {
251         restoreExitedViews();
252         if (mEnterTransitionCoordinator != null) {
253             mEnterTransitionCoordinator.stop();
254             mEnterTransitionCoordinator = null;
255         }
256         if (mReturnExitCoordinator != null) {
257             mReturnExitCoordinator.stop();
258             mReturnExitCoordinator = null;
259         }
260     }
261 
onResume(Activity activity)262     public void onResume(Activity activity) {
263         // After orientation change, the onResume can come in before the top Activity has
264         // left, so if the Activity is not top, wait a second for the top Activity to exit.
265         if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
266             restoreExitedViews();
267             restoreReenteringViews();
268         } else {
269             activity.mHandler.postDelayed(new Runnable() {
270                 @Override
271                 public void run() {
272                     if (mEnterTransitionCoordinator == null ||
273                             mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
274                         restoreExitedViews();
275                         restoreReenteringViews();
276                     } else if (mEnterTransitionCoordinator.isReturning()) {
277                         mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
278                             mEnterTransitionCoordinator = null;
279                         });
280                     }
281                 }
282             }, 1000);
283         }
284     }
285 
clear()286     public void clear() {
287         mPendingExitNames = null;
288         mExitingFrom = null;
289         mExitingTo = null;
290         mExitingToView = null;
291         mCalledExitCoordinator = null;
292         mEnterTransitionCoordinator = null;
293         mEnterActivityOptions = null;
294         mExitTransitionCoordinators = null;
295     }
296 
restoreExitedViews()297     private void restoreExitedViews() {
298         if (mCalledExitCoordinator != null) {
299             mCalledExitCoordinator.resetViews();
300             mCalledExitCoordinator = null;
301         }
302     }
303 
restoreReenteringViews()304     private void restoreReenteringViews() {
305         if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
306                 !mEnterTransitionCoordinator.isCrossTask()) {
307             mEnterTransitionCoordinator.forceViewsToAppear();
308             mExitingFrom = null;
309             mExitingTo = null;
310             mExitingToView = null;
311         }
312     }
313 
startExitBackTransition(final Activity activity)314     public boolean startExitBackTransition(final Activity activity) {
315         ArrayList<String> pendingExitNames = getPendingExitNames();
316         if (pendingExitNames == null || mCalledExitCoordinator != null) {
317             return false;
318         } else {
319             if (!mHasExited) {
320                 mHasExited = true;
321                 Transition enterViewsTransition = null;
322                 ViewGroup decor = null;
323                 boolean delayExitBack = false;
324                 if (mEnterTransitionCoordinator != null) {
325                     enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
326                     decor = mEnterTransitionCoordinator.getDecor();
327                     delayExitBack = mEnterTransitionCoordinator.cancelEnter();
328                     mEnterTransitionCoordinator = null;
329                     if (enterViewsTransition != null && decor != null) {
330                         enterViewsTransition.pause(decor);
331                     }
332                 }
333 
334                 mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
335                         activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
336                         null, null, true);
337                 if (enterViewsTransition != null && decor != null) {
338                     enterViewsTransition.resume(decor);
339                 }
340                 if (delayExitBack && decor != null) {
341                     final ViewGroup finalDecor = decor;
342                     OneShotPreDrawListener.add(decor, () -> {
343                         if (mReturnExitCoordinator != null) {
344                             mReturnExitCoordinator.startExit(activity.mResultCode,
345                                     activity.mResultData);
346                         }
347                     });
348                 } else {
349                     mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
350                 }
351             }
352             return true;
353         }
354     }
355 
isTransitionRunning()356     public boolean isTransitionRunning() {
357         // Note that *only* enter *or* exit will be running at any given time
358         if (mEnterTransitionCoordinator != null) {
359             if (mEnterTransitionCoordinator.isTransitionRunning()) {
360                 return true;
361             }
362         }
363         if (mCalledExitCoordinator != null) {
364             if (mCalledExitCoordinator.isTransitionRunning()) {
365                 return true;
366             }
367         }
368         if (mReturnExitCoordinator != null) {
369             if (mReturnExitCoordinator.isTransitionRunning()) {
370                 return true;
371             }
372         }
373         return false;
374     }
375 
startExitOutTransition(Activity activity, Bundle options)376     public void startExitOutTransition(Activity activity, Bundle options) {
377         mEnterTransitionCoordinator = null;
378         if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
379                 mExitTransitionCoordinators == null) {
380             return;
381         }
382         ActivityOptions activityOptions = new ActivityOptions(options);
383         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
384             int key = activityOptions.getExitCoordinatorKey();
385             int index = mExitTransitionCoordinators.indexOfKey(key);
386             if (index >= 0) {
387                 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
388                 mExitTransitionCoordinators.removeAt(index);
389                 if (mCalledExitCoordinator != null) {
390                     mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
391                     mExitingTo = mCalledExitCoordinator.getMappedNames();
392                     mExitingToView = mCalledExitCoordinator.copyMappedViews();
393                     mCalledExitCoordinator.startExit();
394                 }
395             }
396         }
397     }
398 }
399