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