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.SparseArray; 22 import android.view.View; 23 import android.view.ViewGroup; 24 import android.view.ViewTreeObserver; 25 import android.view.Window; 26 27 import java.lang.ref.WeakReference; 28 import java.util.ArrayList; 29 30 /** 31 * This class contains all persistence-related functionality for Activity Transitions. 32 * Activities start exit and enter Activity Transitions through this class. 33 */ 34 class ActivityTransitionState { 35 36 private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements"; 37 38 private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom"; 39 40 private static final String EXITING_MAPPED_TO = "android:exitingMappedTo"; 41 42 /** 43 * The shared elements that the calling Activity has said that they transferred to this 44 * Activity. 45 */ 46 private ArrayList<String> mEnteringNames; 47 48 /** 49 * The names of shared elements that were shared to the called Activity. 50 */ 51 private ArrayList<String> mExitingFrom; 52 53 /** 54 * The names of local Views that were shared out, mapped to those elements in mExitingFrom. 55 */ 56 private ArrayList<String> mExitingTo; 57 58 /** 59 * The local Views that were shared out, mapped to those elements in mExitingFrom. 60 */ 61 private ArrayList<View> mExitingToView; 62 63 /** 64 * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore 65 * Visibility of exited Views. 66 */ 67 private ExitTransitionCoordinator mCalledExitCoordinator; 68 69 /** 70 * The ExitTransitionCoordinator used to return to a previous Activity when called with 71 * {@link android.app.Activity#finishAfterTransition()}. 72 */ 73 private ExitTransitionCoordinator mReturnExitCoordinator; 74 75 /** 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. 78 */ 79 private EnterTransitionCoordinator mEnterTransitionCoordinator; 80 81 /** 82 * ActivityOptions used on entering this Activity. 83 */ 84 private ActivityOptions mEnterActivityOptions; 85 86 /** 87 * Has an exit transition been started? If so, we don't want to double-exit. 88 */ 89 private boolean mHasExited; 90 91 /** 92 * Postpone painting and starting the enter transition until this is false. 93 */ 94 private boolean mIsEnterPostponed; 95 96 /** 97 * Potential exit transition coordinators. 98 */ 99 private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators; 100 101 /** 102 * Next key for mExitTransitionCoordinator. 103 */ 104 private int mExitTransitionCoordinatorsKey = 1; 105 106 private boolean mIsEnterTriggered; 107 ActivityTransitionState()108 public ActivityTransitionState() { 109 } 110 addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator)111 public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) { 112 if (mExitTransitionCoordinators == null) { 113 mExitTransitionCoordinators = 114 new SparseArray<WeakReference<ExitTransitionCoordinator>>(); 115 } 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); 123 } 124 } 125 int newKey = mExitTransitionCoordinatorsKey++; 126 mExitTransitionCoordinators.append(newKey, ref); 127 return newKey; 128 } 129 readState(Bundle bundle)130 public void readState(Bundle bundle) { 131 if (bundle != null) { 132 if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) { 133 mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS); 134 } 135 if (mEnterTransitionCoordinator == null) { 136 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM); 137 mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO); 138 } 139 } 140 } 141 saveState(Bundle bundle)142 public void saveState(Bundle bundle) { 143 if (mEnteringNames != null) { 144 bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames); 145 } 146 if (mExitingFrom != null) { 147 bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom); 148 bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo); 149 } 150 } 151 setEnterActivityOptions(Activity activity, ActivityOptions options)152 public void setEnterActivityOptions(Activity activity, ActivityOptions options) { 153 final Window window = activity.getWindow(); 154 if (window == null) { 155 return; 156 } 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(); 168 if (result != 0) { 169 activity.onActivityReenter(result, mEnterActivityOptions.getResultData()); 170 } 171 } 172 } 173 } 174 enterReady(Activity activity)175 public void enterReady(Activity activity) { 176 if (mEnterActivityOptions == null || mIsEnterTriggered) { 177 return; 178 } 179 mIsEnterTriggered = true; 180 mHasExited = false; 181 ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames(); 182 ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver(); 183 if (mEnterActivityOptions.isReturning()) { 184 restoreExitedViews(); 185 activity.getWindow().getDecorView().setVisibility(View.VISIBLE); 186 } 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()); 193 } 194 195 if (!mIsEnterPostponed) { 196 startEnter(); 197 } 198 } 199 postponeEnterTransition()200 public void postponeEnterTransition() { 201 mIsEnterPostponed = true; 202 } 203 startPostponedEnterTransition()204 public void startPostponedEnterTransition() { 205 if (mIsEnterPostponed) { 206 mIsEnterPostponed = false; 207 if (mEnterTransitionCoordinator != null) { 208 startEnter(); 209 } 210 } 211 } 212 startEnter()213 private void startEnter() { 214 if (mEnterTransitionCoordinator.isReturning()) { 215 if (mExitingToView != null) { 216 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, 217 mExitingToView); 218 } else { 219 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); 220 } 221 } else { 222 mEnterTransitionCoordinator.namedViewsReady(null, null); 223 mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames(); 224 } 225 226 mExitingFrom = null; 227 mExitingTo = null; 228 mExitingToView = null; 229 mEnterActivityOptions = null; 230 } 231 onStop()232 public void onStop() { 233 restoreExitedViews(); 234 if (mEnterTransitionCoordinator != null) { 235 mEnterTransitionCoordinator.stop(); 236 mEnterTransitionCoordinator = null; 237 } 238 if (mReturnExitCoordinator != null) { 239 mReturnExitCoordinator.stop(); 240 mReturnExitCoordinator = null; 241 } 242 } 243 onResume(Activity activity, boolean isTopOfTask)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(); 250 } else { 251 activity.mHandler.postDelayed(new Runnable() { 252 @Override 253 public void run() { 254 if (mEnterTransitionCoordinator == null || 255 mEnterTransitionCoordinator.isWaitingForRemoteExit()) { 256 restoreExitedViews(); 257 restoreReenteringViews(); 258 } 259 } 260 }, 1000); 261 } 262 } 263 clear()264 public void clear() { 265 mEnteringNames = null; 266 mExitingFrom = null; 267 mExitingTo = null; 268 mExitingToView = null; 269 mCalledExitCoordinator = null; 270 mEnterTransitionCoordinator = null; 271 mEnterActivityOptions = null; 272 mExitTransitionCoordinators = null; 273 } 274 restoreExitedViews()275 private void restoreExitedViews() { 276 if (mCalledExitCoordinator != null) { 277 mCalledExitCoordinator.resetViews(); 278 mCalledExitCoordinator = null; 279 } 280 } 281 restoreReenteringViews()282 private void restoreReenteringViews() { 283 if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() && 284 !mEnterTransitionCoordinator.isCrossTask()) { 285 mEnterTransitionCoordinator.forceViewsToAppear(); 286 mExitingFrom = null; 287 mExitingTo = null; 288 mExitingToView = null; 289 } 290 } 291 startExitBackTransition(final Activity activity)292 public boolean startExitBackTransition(final Activity activity) { 293 if (mEnteringNames == null || mCalledExitCoordinator != null) { 294 return false; 295 } else { 296 if (!mHasExited) { 297 mHasExited = true; 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); 308 } 309 } 310 311 mReturnExitCoordinator = new ExitTransitionCoordinator(activity, 312 activity.getWindow(), activity.mEnterTransitionListener, mEnteringNames, 313 null, null, true); 314 if (enterViewsTransition != null && decor != null) { 315 enterViewsTransition.resume(decor); 316 } 317 if (delayExitBack && decor != null) { 318 final ViewGroup finalDecor = decor; 319 decor.getViewTreeObserver().addOnPreDrawListener( 320 new ViewTreeObserver.OnPreDrawListener() { 321 @Override 322 public boolean onPreDraw() { 323 finalDecor.getViewTreeObserver().removeOnPreDrawListener(this); 324 if (mReturnExitCoordinator != null) { 325 mReturnExitCoordinator.startExit(activity.mResultCode, 326 activity.mResultData); 327 } 328 return true; 329 } 330 }); 331 } else { 332 mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); 333 } 334 } 335 return true; 336 } 337 } 338 startExitOutTransition(Activity activity, Bundle options)339 public void startExitOutTransition(Activity activity, Bundle options) { 340 mEnterTransitionCoordinator = null; 341 if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) || 342 mExitTransitionCoordinators == null) { 343 return; 344 } 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); 349 if (index >= 0) { 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(); 357 } 358 } 359 } 360 } 361 } 362