2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 import android.os.Bundle;
19 import android.os.ResultReceiver;
20 import android.transition.Transition;
21 import android.util.SparseArray;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.ViewTreeObserver;
25 import android.view.Window;
27 import java.lang.ref.WeakReference;
28 import java.util.ArrayList;
31 * This class contains all persistence-related functionality for Activity Transitions.
32 * Activities start exit and enter Activity Transitions through this class.
34 class ActivityTransitionState {
36 private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
38 private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
40 private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
43 * The shared elements that the calling Activity has said that they transferred to this
46 private ArrayList<String> mEnteringNames;
49 * The names of shared elements that were shared to the called Activity.
51 private ArrayList<String> mExitingFrom;
54 * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
56 private ArrayList<String> mExitingTo;
59 * The local Views that were shared out, mapped to those elements in mExitingFrom.
61 private ArrayList<View> mExitingToView;
64 * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
65 * Visibility of exited Views.
67 private ExitTransitionCoordinator mCalledExitCoordinator;
70 * The ExitTransitionCoordinator used to return to a previous Activity when called with
71 * {@link android.app.Activity#finishAfterTransition()}.
73 private ExitTransitionCoordinator mReturnExitCoordinator;
76 * We must be able to cancel entering transitions to stop changing the Window to
77 * opaque when we exit before making the Window opaque.
79 private EnterTransitionCoordinator mEnterTransitionCoordinator;
82 * ActivityOptions used on entering this Activity.
84 private ActivityOptions mEnterActivityOptions;
87 * Has an exit transition been started? If so, we don't want to double-exit.
89 private boolean mHasExited;
92 * Postpone painting and starting the enter transition until this is false.
94 private boolean mIsEnterPostponed;
97 * Potential exit transition coordinators.
99 private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
102 * Next key for mExitTransitionCoordinator.
104 private int mExitTransitionCoordinatorsKey = 1;
106 private boolean mIsEnterTriggered;
108 public ActivityTransitionState() {
111 public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
112 if (mExitTransitionCoordinators == null) {
113 mExitTransitionCoordinators =
114 new SparseArray<WeakReference<ExitTransitionCoordinator>>();
116 WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
117 // clean up old references:
118 for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
119 WeakReference<ExitTransitionCoordinator> oldRef
120 = mExitTransitionCoordinators.valueAt(i);
121 if (oldRef.get() == null) {
122 mExitTransitionCoordinators.removeAt(i);
125 int newKey = mExitTransitionCoordinatorsKey++;
126 mExitTransitionCoordinators.append(newKey, ref);
130 public void readState(Bundle bundle) {
131 if (bundle != null) {
132 if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
133 mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
135 if (mEnterTransitionCoordinator == null) {
136 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
137 mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
142 public void saveState(Bundle bundle) {
143 if (mEnteringNames != null) {
144 bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
146 if (mExitingFrom != null) {
147 bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
148 bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
152 public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
153 final Window window = activity.getWindow();
154 if (window == null) {
157 // ensure Decor View has been created so that the window features are activated
158 window.getDecorView();
159 if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
160 && options != null && mEnterActivityOptions == null
161 && mEnterTransitionCoordinator == null
162 && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
163 mEnterActivityOptions = options;
164 mIsEnterTriggered = false;
165 if (mEnterActivityOptions.isReturning()) {
166 restoreExitedViews();
167 int result = mEnterActivityOptions.getResultCode();
169 activity.onActivityReenter(result, mEnterActivityOptions.getResultData());
175 public void enterReady(Activity activity) {
176 if (mEnterActivityOptions == null || mIsEnterTriggered) {
179 mIsEnterTriggered = true;
181 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
182 ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
183 if (mEnterActivityOptions.isReturning()) {
184 restoreExitedViews();
185 activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
187 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
188 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
189 mEnterActivityOptions.isCrossTask());
190 if (mEnterActivityOptions.isCrossTask()) {
191 mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
192 mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
195 if (!mIsEnterPostponed) {
200 public void postponeEnterTransition() {
201 mIsEnterPostponed = true;
204 public void startPostponedEnterTransition() {
205 if (mIsEnterPostponed) {
206 mIsEnterPostponed = false;
207 if (mEnterTransitionCoordinator != null) {
213 private void startEnter() {
214 if (mEnterTransitionCoordinator.isReturning()) {
215 if (mExitingToView != null) {
216 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
219 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
222 mEnterTransitionCoordinator.namedViewsReady(null, null);
223 mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
228 mExitingToView = null;
229 mEnterActivityOptions = null;
232 public void onStop() {
233 restoreExitedViews();
234 if (mEnterTransitionCoordinator != null) {
235 mEnterTransitionCoordinator.stop();
236 mEnterTransitionCoordinator = null;
238 if (mReturnExitCoordinator != null) {
239 mReturnExitCoordinator.stop();
240 mReturnExitCoordinator = null;
244 public void onResume(Activity activity, boolean isTopOfTask) {
245 // After orientation change, the onResume can come in before the top Activity has
246 // left, so if the Activity is not top, wait a second for the top Activity to exit.
247 if (isTopOfTask || mEnterTransitionCoordinator == null) {
248 restoreExitedViews();
249 restoreReenteringViews();
251 activity.mHandler.postDelayed(new Runnable() {
254 if (mEnterTransitionCoordinator == null ||
255 mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
256 restoreExitedViews();
257 restoreReenteringViews();
264 public void clear() {
265 mEnteringNames = null;
268 mExitingToView = null;
269 mCalledExitCoordinator = null;
270 mEnterTransitionCoordinator = null;
271 mEnterActivityOptions = null;
272 mExitTransitionCoordinators = null;
275 private void restoreExitedViews() {
276 if (mCalledExitCoordinator != null) {
277 mCalledExitCoordinator.resetViews();
278 mCalledExitCoordinator = null;
282 private void restoreReenteringViews() {
283 if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
284 !mEnterTransitionCoordinator.isCrossTask()) {
285 mEnterTransitionCoordinator.forceViewsToAppear();
288 mExitingToView = null;
292 public boolean startExitBackTransition(final Activity activity) {
293 if (mEnteringNames == null || mCalledExitCoordinator != null) {
298 Transition enterViewsTransition = null;
299 ViewGroup decor = null;
300 boolean delayExitBack = false;
301 if (mEnterTransitionCoordinator != null) {
302 enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
303 decor = mEnterTransitionCoordinator.getDecor();
304 delayExitBack = mEnterTransitionCoordinator.cancelEnter();
305 mEnterTransitionCoordinator = null;
306 if (enterViewsTransition != null && decor != null) {
307 enterViewsTransition.pause(decor);
311 mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
312 activity.getWindow(), activity.mEnterTransitionListener, mEnteringNames,
314 if (enterViewsTransition != null && decor != null) {
315 enterViewsTransition.resume(decor);
317 if (delayExitBack && decor != null) {
318 final ViewGroup finalDecor = decor;
319 decor.getViewTreeObserver().addOnPreDrawListener(
320 new ViewTreeObserver.OnPreDrawListener() {
322 public boolean onPreDraw() {
323 finalDecor.getViewTreeObserver().removeOnPreDrawListener(this);
324 if (mReturnExitCoordinator != null) {
325 mReturnExitCoordinator.startExit(activity.mResultCode,
326 activity.mResultData);
332 mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
339 public void startExitOutTransition(Activity activity, Bundle options) {
340 mEnterTransitionCoordinator = null;
341 if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
342 mExitTransitionCoordinators == null) {
345 ActivityOptions activityOptions = new ActivityOptions(options);
346 if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
347 int key = activityOptions.getExitCoordinatorKey();
348 int index = mExitTransitionCoordinators.indexOfKey(key);
350 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
351 mExitTransitionCoordinators.removeAt(index);
352 if (mCalledExitCoordinator != null) {
353 mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
354 mExitingTo = mCalledExitCoordinator.getMappedNames();
355 mExitingToView = mCalledExitCoordinator.copyMappedViews();
356 mCalledExitCoordinator.startExit();