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.app.ActivityOptions.SceneTransitionInfo; 19 import android.content.Intent; 20 import android.os.Bundle; 21 import android.os.ResultReceiver; 22 import android.transition.Transition; 23 import android.util.SparseArray; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.view.Window; 27 28 import com.android.internal.view.OneShotPreDrawListener; 29 30 import java.lang.ref.WeakReference; 31 import java.util.ArrayList; 32 33 /** 34 * This class contains all persistence-related functionality for Activity Transitions. 35 * Activities start exit and enter Activity Transitions through this class. 36 */ 37 class ActivityTransitionState { 38 39 private static final String PENDING_EXIT_SHARED_ELEMENTS = "android:pendingExitSharedElements"; 40 41 private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom"; 42 43 private static final String EXITING_MAPPED_TO = "android:exitingMappedTo"; 44 45 /** 46 * The shared elements that the calling Activity has said that they transferred to this 47 * Activity and will be transferred back during exit animation. 48 */ 49 private ArrayList<String> mPendingExitNames; 50 51 /** 52 * The names of shared elements that were shared to the called Activity. 53 */ 54 private ArrayList<String> mExitingFrom; 55 56 /** 57 * The names of local Views that were shared out, mapped to those elements in mExitingFrom. 58 */ 59 private ArrayList<String> mExitingTo; 60 61 /** 62 * The local Views that were shared out, mapped to those elements in mExitingFrom. 63 */ 64 private ArrayList<View> mExitingToView; 65 66 /** 67 * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore 68 * Visibility of exited Views. 69 */ 70 private ExitTransitionCoordinator mCalledExitCoordinator; 71 72 /** 73 * The ExitTransitionCoordinator used to return to a previous Activity when called with 74 * {@link android.app.Activity#finishAfterTransition()}. 75 */ 76 private ExitTransitionCoordinator mReturnExitCoordinator; 77 78 /** 79 * We must be able to cancel entering transitions to stop changing the Window to 80 * opaque when we exit before making the Window opaque. 81 */ 82 private EnterTransitionCoordinator mEnterTransitionCoordinator; 83 84 /** 85 * {@link SceneTransitionInfo} used on entering this Activity. 86 */ 87 private SceneTransitionInfo mEnterSceneTransitionInfo; 88 89 /** 90 * Has an exit transition been started? If so, we don't want to double-exit. 91 */ 92 private boolean mHasExited; 93 94 /** 95 * Postpone painting and starting the enter transition until this is false. 96 */ 97 private boolean mIsEnterPostponed; 98 99 /** 100 * Potential exit transition coordinators. 101 */ 102 private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators; 103 104 /** 105 * Next key for mExitTransitionCoordinator. 106 */ 107 private int mExitTransitionCoordinatorsKey = 1; 108 109 private boolean mIsEnterTriggered; 110 ActivityTransitionState()111 public ActivityTransitionState() { 112 } 113 addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator)114 public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) { 115 if (mExitTransitionCoordinators == null) { 116 mExitTransitionCoordinators = new SparseArray<>(); 117 } 118 WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator); 119 // clean up old references: 120 for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) { 121 WeakReference<ExitTransitionCoordinator> oldRef 122 = mExitTransitionCoordinators.valueAt(i); 123 if (oldRef.refersTo(null)) { 124 mExitTransitionCoordinators.removeAt(i); 125 } 126 } 127 int newKey = mExitTransitionCoordinatorsKey++; 128 mExitTransitionCoordinators.append(newKey, ref); 129 return newKey; 130 } 131 readState(Bundle bundle)132 public void readState(Bundle bundle) { 133 if (bundle != null) { 134 if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) { 135 mPendingExitNames = bundle.getStringArrayList(PENDING_EXIT_SHARED_ELEMENTS); 136 } 137 if (mEnterTransitionCoordinator == null) { 138 mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM); 139 mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO); 140 } 141 } 142 } 143 144 /** 145 * Returns the element names to be used for exit animation. It caches the list internally so 146 * that it is preserved through activty destroy and restore. 147 */ getPendingExitNames()148 private ArrayList<String> getPendingExitNames() { 149 if (mPendingExitNames == null 150 && mEnterTransitionCoordinator != null 151 && !mEnterTransitionCoordinator.isReturning() 152 ) { 153 mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames(); 154 } 155 return mPendingExitNames; 156 } 157 saveState(Bundle bundle)158 public void saveState(Bundle bundle) { 159 ArrayList<String> pendingExitNames = getPendingExitNames(); 160 if (pendingExitNames != null) { 161 bundle.putStringArrayList(PENDING_EXIT_SHARED_ELEMENTS, pendingExitNames); 162 } 163 if (mExitingFrom != null) { 164 bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom); 165 bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo); 166 } 167 } 168 setEnterSceneTransitionInfo(Activity activity, SceneTransitionInfo info)169 public void setEnterSceneTransitionInfo(Activity activity, SceneTransitionInfo info) { 170 final Window window = activity.getWindow(); 171 if (window == null) { 172 return; 173 } 174 // ensure Decor View has been created so that the window features are activated 175 window.getDecorView(); 176 if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) 177 && info != null && mEnterSceneTransitionInfo == null 178 && mEnterTransitionCoordinator == null) { 179 mEnterSceneTransitionInfo = info; 180 mIsEnterTriggered = false; 181 if (mEnterSceneTransitionInfo.isReturning()) { 182 restoreExitedViews(); 183 int result = mEnterSceneTransitionInfo.getResultCode(); 184 if (result != 0) { 185 Intent intent = mEnterSceneTransitionInfo.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 (mEnterSceneTransitionInfo == null || mIsEnterTriggered) { 197 return; 198 } 199 mIsEnterTriggered = true; 200 mHasExited = false; 201 final ArrayList<String> sharedElementNames = 202 mEnterSceneTransitionInfo.getSharedElementNames(); 203 ResultReceiver resultReceiver = mEnterSceneTransitionInfo.getResultReceiver(); 204 final boolean isReturning = mEnterSceneTransitionInfo.isReturning(); 205 if (isReturning) { 206 restoreExitedViews(); 207 activity.getWindow().getDecorView().setVisibility(View.VISIBLE); 208 } 209 getPendingExitNames(); // Set mPendingExitNames before resetting mEnterTransitionCoordinator 210 mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity, 211 resultReceiver, sharedElementNames, mEnterSceneTransitionInfo.isReturning(), 212 mEnterSceneTransitionInfo.isCrossTask()); 213 if (mEnterSceneTransitionInfo.isCrossTask() && sharedElementNames != null) { 214 mExitingFrom = new ArrayList<>(sharedElementNames); 215 mExitingTo = new ArrayList<>(sharedElementNames); 216 } 217 218 if (!mIsEnterPostponed) { 219 startEnter(); 220 } 221 } 222 postponeEnterTransition()223 public void postponeEnterTransition() { 224 mIsEnterPostponed = true; 225 } 226 startPostponedEnterTransition()227 public void startPostponedEnterTransition() { 228 if (mIsEnterPostponed) { 229 mIsEnterPostponed = false; 230 if (mEnterTransitionCoordinator != null) { 231 startEnter(); 232 } 233 } 234 } 235 startEnter()236 private void startEnter() { 237 if (mEnterTransitionCoordinator.isReturning()) { 238 if (mExitingToView != null) { 239 mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo, 240 mExitingToView); 241 } else { 242 mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo); 243 } 244 } else { 245 mEnterTransitionCoordinator.namedViewsReady(null, null); 246 mPendingExitNames = null; 247 } 248 249 mExitingFrom = null; 250 mExitingTo = null; 251 mExitingToView = null; 252 mEnterSceneTransitionInfo = null; 253 } 254 onStop(Activity activity)255 public void onStop(Activity activity) { 256 restoreExitedViews(); 257 if (mEnterTransitionCoordinator != null) { 258 getPendingExitNames(); // Set mPendingExitNames before clearing 259 mEnterTransitionCoordinator.stop(); 260 mEnterTransitionCoordinator = null; 261 } 262 if (mReturnExitCoordinator != null) { 263 mReturnExitCoordinator.stop(activity); 264 mReturnExitCoordinator = null; 265 } 266 } 267 onResume(Activity activity)268 public void onResume(Activity activity) { 269 // After orientation change, the onResume can come in before the top Activity has 270 // left, so if the Activity is not top, wait a second for the top Activity to exit. 271 if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) { 272 restoreExitedViews(); 273 restoreReenteringViews(); 274 } else { 275 activity.mHandler.postDelayed(new Runnable() { 276 @Override 277 public void run() { 278 if (mEnterTransitionCoordinator == null || 279 mEnterTransitionCoordinator.isWaitingForRemoteExit()) { 280 restoreExitedViews(); 281 restoreReenteringViews(); 282 } else if (mEnterTransitionCoordinator.isReturning()) { 283 mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> { 284 getPendingExitNames(); // Set mPendingExitNames before clearing 285 mEnterTransitionCoordinator = null; 286 }); 287 } 288 } 289 }, 1000); 290 } 291 } 292 clear()293 public void clear() { 294 mPendingExitNames = null; 295 mExitingFrom = null; 296 mExitingTo = null; 297 mExitingToView = null; 298 mCalledExitCoordinator = null; 299 mEnterTransitionCoordinator = null; 300 mEnterSceneTransitionInfo = null; 301 mExitTransitionCoordinators = null; 302 } 303 restoreExitedViews()304 private void restoreExitedViews() { 305 if (mCalledExitCoordinator != null) { 306 mCalledExitCoordinator.resetViews(); 307 mCalledExitCoordinator = null; 308 } 309 } 310 restoreReenteringViews()311 private void restoreReenteringViews() { 312 if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() && 313 !mEnterTransitionCoordinator.isCrossTask()) { 314 mEnterTransitionCoordinator.forceViewsToAppear(); 315 mExitingFrom = null; 316 mExitingTo = null; 317 mExitingToView = null; 318 } 319 } 320 startExitBackTransition(final Activity activity)321 public boolean startExitBackTransition(final Activity activity) { 322 ArrayList<String> pendingExitNames = getPendingExitNames(); 323 if (pendingExitNames == null || mCalledExitCoordinator != null) { 324 return false; 325 } else { 326 if (!mHasExited) { 327 mHasExited = true; 328 Transition enterViewsTransition = null; 329 ViewGroup decor = null; 330 boolean delayExitBack = false; 331 if (mEnterTransitionCoordinator != null) { 332 enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition(); 333 decor = mEnterTransitionCoordinator.getDecor(); 334 delayExitBack = mEnterTransitionCoordinator.cancelEnter(); 335 mEnterTransitionCoordinator = null; 336 if (enterViewsTransition != null && decor != null) { 337 enterViewsTransition.pause(decor); 338 } 339 } 340 341 mReturnExitCoordinator = new ExitTransitionCoordinator( 342 new ExitTransitionCoordinator.ActivityExitTransitionCallbacks(activity), 343 activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames, 344 null, null, true); 345 if (enterViewsTransition != null && decor != null) { 346 enterViewsTransition.resume(decor); 347 } 348 if (delayExitBack && decor != null) { 349 final ViewGroup finalDecor = decor; 350 OneShotPreDrawListener.add(decor, () -> { 351 if (mReturnExitCoordinator != null) { 352 mReturnExitCoordinator.startExit(activity); 353 } 354 }); 355 } else { 356 mReturnExitCoordinator.startExit(activity); 357 } 358 } 359 return true; 360 } 361 } 362 isTransitionRunning()363 public boolean isTransitionRunning() { 364 // Note that *only* enter *or* exit will be running at any given time 365 if (mEnterTransitionCoordinator != null) { 366 if (mEnterTransitionCoordinator.isTransitionRunning()) { 367 return true; 368 } 369 } 370 if (mCalledExitCoordinator != null) { 371 if (mCalledExitCoordinator.isTransitionRunning()) { 372 return true; 373 } 374 } 375 if (mReturnExitCoordinator != null) { 376 if (mReturnExitCoordinator.isTransitionRunning()) { 377 return true; 378 } 379 } 380 return false; 381 } 382 startExitOutTransition(Activity activity, Bundle options)383 public void startExitOutTransition(Activity activity, Bundle options) { 384 getPendingExitNames(); // Set mPendingExitNames before clearing mEnterTransitionCoordinator 385 mEnterTransitionCoordinator = null; 386 if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) || 387 mExitTransitionCoordinators == null) { 388 return; 389 } 390 final ActivityOptions activityOptions = new ActivityOptions(options); 391 final SceneTransitionInfo info = activityOptions.getSceneTransitionInfo(); 392 if (info != null) { 393 int key = info.getExitCoordinatorKey(); 394 int index = mExitTransitionCoordinators.indexOfKey(key); 395 if (index >= 0) { 396 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get(); 397 mExitTransitionCoordinators.removeAt(index); 398 if (mCalledExitCoordinator != null) { 399 mExitingFrom = mCalledExitCoordinator.getAcceptedNames(); 400 mExitingTo = mCalledExitCoordinator.getMappedNames(); 401 mExitingToView = mCalledExitCoordinator.copyMappedViews(); 402 mCalledExitCoordinator.startExit(); 403 } 404 } 405 } 406 } 407 } 408