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 */
16package android.app;
17
18import android.os.Bundle;
19import android.os.ResultReceiver;
20import android.transition.Transition;
21import android.util.ArrayMap;
22import android.util.SparseArray;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewTreeObserver;
26import android.view.Window;
27
28import java.lang.ref.WeakReference;
29import 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 */
35class 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
109    public ActivityTransitionState() {
110    }
111
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
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
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
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
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
190    public void postponeEnterTransition() {
191        mIsEnterPostponed = true;
192    }
193
194    public void startPostponedEnterTransition() {
195        if (mIsEnterPostponed) {
196            mIsEnterPostponed = false;
197            if (mEnterTransitionCoordinator != null) {
198                startEnter();
199            }
200        }
201    }
202
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
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
234    public void onResume() {
235        restoreExitedViews();
236    }
237
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
249    private void restoreExitedViews() {
250        if (mCalledExitCoordinator != null) {
251            mCalledExitCoordinator.resetViews();
252            mCalledExitCoordinator = null;
253        }
254    }
255
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
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