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