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.os.Bundle;
19 import android.os.ResultReceiver;
20 import android.transition.Transition;
21 import android.util.ArrayMap;
22 import android.util.SparseArray;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.ViewTreeObserver;
26 import android.view.Window;
27 
28 import java.lang.ref.WeakReference;
29 import java.util.ArrayList;
30 
31 /**
32  * This class contains all persistence-related functionality for Activity Transitions.
33  * Activities start exit and enter Activity Transitions through this class.
34  */
35 class ActivityTransitionState {
36 
37     private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
38 
39     private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
40 
41     private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
42 
43     /**
44      * The shared elements that the calling Activity has said that they transferred to this
45      * Activity.
46      */
47     private ArrayList<String> mEnteringNames;
48 
49     /**
50      * The names of shared elements that were shared to the called Activity.
51      */
52     private ArrayList<String> mExitingFrom;
53 
54     /**
55      * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
56      */
57     private ArrayList<String> mExitingTo;
58 
59     /**
60      * The local Views that were shared out, mapped to those elements in mExitingFrom.
61      */
62     private ArrayList<View> mExitingToView;
63 
64     /**
65      * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
66      * Visibility of exited Views.
67      */
68     private ExitTransitionCoordinator mCalledExitCoordinator;
69 
70     /**
71      * The ExitTransitionCoordinator used to return to a previous Activity when called with
72      * {@link android.app.Activity#finishAfterTransition()}.
73      */
74     private ExitTransitionCoordinator mReturnExitCoordinator;
75 
76     /**
77      * We must be able to cancel entering transitions to stop changing the Window to
78      * opaque when we exit before making the Window opaque.
79      */
80     private EnterTransitionCoordinator mEnterTransitionCoordinator;
81 
82     /**
83      * ActivityOptions used on entering this Activity.
84      */
85     private ActivityOptions mEnterActivityOptions;
86 
87     /**
88      * Has an exit transition been started? If so, we don't want to double-exit.
89      */
90     private boolean mHasExited;
91 
92     /**
93      * Postpone painting and starting the enter transition until this is false.
94      */
95     private boolean mIsEnterPostponed;
96 
97     /**
98      * Potential exit transition coordinators.
99      */
100     private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
101 
102     /**
103      * Next key for mExitTransitionCoordinator.
104      */
105     private int mExitTransitionCoordinatorsKey = 1;
106 
107     private boolean mIsEnterTriggered;
108 
ActivityTransitionState()109     public ActivityTransitionState() {
110     }
111 
addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator)112     public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
113         if (mExitTransitionCoordinators == null) {
114             mExitTransitionCoordinators =
115                     new SparseArray<WeakReference<ExitTransitionCoordinator>>();
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                 mEnteringNames = bundle.getStringArrayList(ENTERING_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 
saveState(Bundle bundle)143     public void saveState(Bundle bundle) {
144         if (mEnteringNames != null) {
145             bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
146         }
147         if (mExitingFrom != null) {
148             bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
149             bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
150         }
151     }
152 
setEnterActivityOptions(Activity activity, ActivityOptions options)153     public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
154         if (activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
155                 && options != null && mEnterActivityOptions == null
156                 && mEnterTransitionCoordinator == null
157                 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
158             mEnterActivityOptions = options;
159             mIsEnterTriggered = false;
160             if (mEnterActivityOptions.isReturning()) {
161                 restoreExitedViews();
162                 int result = mEnterActivityOptions.getResultCode();
163                 if (result != 0) {
164                     activity.onActivityReenter(result, mEnterActivityOptions.getResultData());
165                 }
166             }
167         }
168     }
169 
enterReady(Activity activity)170     public void enterReady(Activity activity) {
171         if (mEnterActivityOptions == null || mIsEnterTriggered) {
172             return;
173         }
174         mIsEnterTriggered = true;
175         mHasExited = false;
176         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
177         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
178         if (mEnterActivityOptions.isReturning()) {
179             restoreExitedViews();
180             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
181         }
182         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
183                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning());
184 
185         if (!mIsEnterPostponed) {
186             startEnter();
187         }
188     }
189 
postponeEnterTransition()190     public void postponeEnterTransition() {
191         mIsEnterPostponed = true;
192     }
193 
startPostponedEnterTransition()194     public void startPostponedEnterTransition() {
195         if (mIsEnterPostponed) {
196             mIsEnterPostponed = false;
197             if (mEnterTransitionCoordinator != null) {
198                 startEnter();
199             }
200         }
201     }
202 
startEnter()203     private void startEnter() {
204         if (mEnterActivityOptions.isReturning()) {
205             if (mExitingToView != null) {
206                 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
207                         mExitingToView);
208             } else {
209                 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
210             }
211         } else {
212             mEnterTransitionCoordinator.namedViewsReady(null, null);
213             mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
214         }
215 
216         mExitingFrom = null;
217         mExitingTo = null;
218         mExitingToView = null;
219         mEnterActivityOptions = null;
220     }
221 
onStop()222     public void onStop() {
223         restoreExitedViews();
224         if (mEnterTransitionCoordinator != null) {
225             mEnterTransitionCoordinator.stop();
226             mEnterTransitionCoordinator = null;
227         }
228         if (mReturnExitCoordinator != null) {
229             mReturnExitCoordinator.stop();
230             mReturnExitCoordinator = null;
231         }
232     }
233 
onResume()234     public void onResume() {
235         restoreExitedViews();
236     }
237 
clear()238     public void clear() {
239         mEnteringNames = null;
240         mExitingFrom = null;
241         mExitingTo = null;
242         mExitingToView = null;
243         mCalledExitCoordinator = null;
244         mEnterTransitionCoordinator = null;
245         mEnterActivityOptions = null;
246         mExitTransitionCoordinators = null;
247     }
248 
restoreExitedViews()249     private void restoreExitedViews() {
250         if (mCalledExitCoordinator != null) {
251             mCalledExitCoordinator.resetViews();
252             mCalledExitCoordinator = null;
253         }
254     }
255 
startExitBackTransition(final Activity activity)256     public boolean startExitBackTransition(final Activity activity) {
257         if (mEnteringNames == null) {
258             return false;
259         } else {
260             if (!mHasExited) {
261                 mHasExited = true;
262                 Transition enterViewsTransition = null;
263                 ViewGroup decor = null;
264                 boolean delayExitBack = false;
265                 if (mEnterTransitionCoordinator != null) {
266                     enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
267                     decor = mEnterTransitionCoordinator.getDecor();
268                     delayExitBack = mEnterTransitionCoordinator.cancelEnter();
269                     mEnterTransitionCoordinator = null;
270                     if (enterViewsTransition != null && decor != null) {
271                         enterViewsTransition.pause(decor);
272                     }
273                 }
274 
275                 mReturnExitCoordinator =
276                         new ExitTransitionCoordinator(activity, mEnteringNames, null, null, true);
277                 if (enterViewsTransition != null && decor != null) {
278                     enterViewsTransition.resume(decor);
279                 }
280                 if (delayExitBack && decor != null) {
281                     final ViewGroup finalDecor = decor;
282                     decor.getViewTreeObserver().addOnPreDrawListener(
283                             new ViewTreeObserver.OnPreDrawListener() {
284                                 @Override
285                                 public boolean onPreDraw() {
286                                     finalDecor.getViewTreeObserver().removeOnPreDrawListener(this);
287                                     if (mReturnExitCoordinator != null) {
288                                         mReturnExitCoordinator.startExit(activity.mResultCode,
289                                                 activity.mResultData);
290                                     }
291                                     return true;
292                                 }
293                             });
294                 } else {
295                     mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
296                 }
297             }
298             return true;
299         }
300     }
301 
startExitOutTransition(Activity activity, Bundle options)302     public void startExitOutTransition(Activity activity, Bundle options) {
303         if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
304             return;
305         }
306         ActivityOptions activityOptions = new ActivityOptions(options);
307         mEnterTransitionCoordinator = null;
308         if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
309             int key = activityOptions.getExitCoordinatorKey();
310             int index = mExitTransitionCoordinators.indexOfKey(key);
311             if (index >= 0) {
312                 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
313                 mExitTransitionCoordinators.removeAt(index);
314                 if (mCalledExitCoordinator != null) {
315                     mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
316                     mExitingTo = mCalledExitCoordinator.getMappedNames();
317                     mExitingToView = mCalledExitCoordinator.copyMappedViews();
318                     mCalledExitCoordinator.startExit();
319                 }
320             }
321         }
322     }
323 }
324