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.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ObjectAnimator; 21 import android.app.SharedElementCallback.OnSharedElementsReadyListener; 22 import android.content.Intent; 23 import android.graphics.Color; 24 import android.graphics.Matrix; 25 import android.graphics.RectF; 26 import android.graphics.drawable.ColorDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.ResultReceiver; 33 import android.transition.Transition; 34 import android.transition.TransitionListenerAdapter; 35 import android.transition.TransitionManager; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.Window; 39 40 import com.android.internal.view.OneShotPreDrawListener; 41 42 import java.util.ArrayList; 43 44 /** 45 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 46 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 47 * the reentry of the Scene when coming back from the called Activity. 48 */ 49 class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 50 private static final String TAG = "ExitTransitionCoordinator"; 51 private static final long MAX_WAIT_MS = 1000; 52 53 private Bundle mSharedElementBundle; 54 private boolean mExitNotified; 55 private boolean mSharedElementNotified; 56 private Activity mActivity; 57 private boolean mIsBackgroundReady; 58 private boolean mIsCanceled; 59 private Handler mHandler; 60 private ObjectAnimator mBackgroundAnimator; 61 private boolean mIsHidden; 62 private Bundle mExitSharedElementBundle; 63 private boolean mIsExitStarted; 64 private boolean mSharedElementsHidden; 65 private HideSharedElementsCallback mHideSharedElementsCallback; 66 ExitTransitionCoordinator(Activity activity, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning)67 public ExitTransitionCoordinator(Activity activity, Window window, 68 SharedElementCallback listener, ArrayList<String> names, 69 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { 70 super(window, names, listener, isReturning); 71 viewsReady(mapSharedElements(accepted, mapped)); 72 stripOffscreenViews(); 73 mIsBackgroundReady = !isReturning; 74 mActivity = activity; 75 } 76 setHideSharedElementsCallback(HideSharedElementsCallback callback)77 void setHideSharedElementsCallback(HideSharedElementsCallback callback) { 78 mHideSharedElementsCallback = callback; 79 } 80 81 @Override onReceiveResult(int resultCode, Bundle resultData)82 protected void onReceiveResult(int resultCode, Bundle resultData) { 83 switch (resultCode) { 84 case MSG_SET_REMOTE_RECEIVER: 85 stopCancel(); 86 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 87 if (mIsCanceled) { 88 mResultReceiver.send(MSG_CANCEL, null); 89 mResultReceiver = null; 90 } else { 91 notifyComplete(); 92 } 93 break; 94 case MSG_HIDE_SHARED_ELEMENTS: 95 stopCancel(); 96 if (!mIsCanceled) { 97 hideSharedElements(); 98 } 99 break; 100 case MSG_START_EXIT_TRANSITION: 101 mHandler.removeMessages(MSG_CANCEL); 102 startExit(); 103 break; 104 case MSG_SHARED_ELEMENT_DESTINATION: 105 mExitSharedElementBundle = resultData; 106 sharedElementExitBack(); 107 break; 108 case MSG_CANCEL: 109 mIsCanceled = true; 110 finish(); 111 break; 112 } 113 } 114 stopCancel()115 private void stopCancel() { 116 if (mHandler != null) { 117 mHandler.removeMessages(MSG_CANCEL); 118 } 119 } 120 delayCancel()121 private void delayCancel() { 122 if (mHandler != null) { 123 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 124 } 125 } 126 resetViews()127 public void resetViews() { 128 ViewGroup decorView = getDecor(); 129 if (decorView != null) { 130 TransitionManager.endTransitions(decorView); 131 } 132 if (mTransitioningViews != null) { 133 showViews(mTransitioningViews, true); 134 setTransitioningViewsVisiblity(View.VISIBLE, true); 135 } 136 showViews(mSharedElements, true); 137 mIsHidden = true; 138 if (!mIsReturning && decorView != null) { 139 decorView.suppressLayout(false); 140 } 141 moveSharedElementsFromOverlay(); 142 clearState(); 143 } 144 sharedElementExitBack()145 private void sharedElementExitBack() { 146 final ViewGroup decorView = getDecor(); 147 if (decorView != null) { 148 decorView.suppressLayout(true); 149 } 150 if (decorView != null && mExitSharedElementBundle != null && 151 !mExitSharedElementBundle.isEmpty() && 152 !mSharedElements.isEmpty() && getSharedElementTransition() != null) { 153 startTransition(new Runnable() { 154 public void run() { 155 startSharedElementExit(decorView); 156 } 157 }); 158 } else { 159 sharedElementTransitionComplete(); 160 } 161 } 162 startSharedElementExit(final ViewGroup decorView)163 private void startSharedElementExit(final ViewGroup decorView) { 164 Transition transition = getSharedElementExitTransition(); 165 transition.addListener(new TransitionListenerAdapter() { 166 @Override 167 public void onTransitionEnd(Transition transition) { 168 transition.removeListener(this); 169 if (isViewsTransitionComplete()) { 170 delayCancel(); 171 } 172 } 173 }); 174 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 175 mSharedElementNames); 176 OneShotPreDrawListener.add(decorView, () -> { 177 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 178 }); 179 setGhostVisibility(View.INVISIBLE); 180 scheduleGhostVisibilityChange(View.INVISIBLE); 181 if (mListener != null) { 182 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, 183 sharedElementSnapshots); 184 } 185 TransitionManager.beginDelayedTransition(decorView, transition); 186 scheduleGhostVisibilityChange(View.VISIBLE); 187 setGhostVisibility(View.VISIBLE); 188 decorView.invalidate(); 189 } 190 hideSharedElements()191 private void hideSharedElements() { 192 moveSharedElementsFromOverlay(); 193 if (mHideSharedElementsCallback != null) { 194 mHideSharedElementsCallback.hideSharedElements(); 195 } 196 if (!mIsHidden) { 197 hideViews(mSharedElements); 198 } 199 mSharedElementsHidden = true; 200 finishIfNecessary(); 201 } 202 startExit()203 public void startExit() { 204 if (!mIsExitStarted) { 205 backgroundAnimatorComplete(); 206 mIsExitStarted = true; 207 pauseInput(); 208 ViewGroup decorView = getDecor(); 209 if (decorView != null) { 210 decorView.suppressLayout(true); 211 } 212 moveSharedElementsToOverlay(); 213 startTransition(new Runnable() { 214 @Override 215 public void run() { 216 if (mActivity != null) { 217 beginTransitions(); 218 } else { 219 startExitTransition(); 220 } 221 } 222 }); 223 } 224 } 225 startExit(int resultCode, Intent data)226 public void startExit(int resultCode, Intent data) { 227 if (!mIsExitStarted) { 228 mIsExitStarted = true; 229 pauseInput(); 230 ViewGroup decorView = getDecor(); 231 if (decorView != null) { 232 decorView.suppressLayout(true); 233 } 234 mHandler = new Handler() { 235 @Override 236 public void handleMessage(Message msg) { 237 mIsCanceled = true; 238 finish(); 239 } 240 }; 241 delayCancel(); 242 moveSharedElementsToOverlay(); 243 if (decorView != null && decorView.getBackground() == null) { 244 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 245 } 246 final boolean targetsM = decorView == null || decorView.getContext() 247 .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M; 248 ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames : 249 mAllSharedElementNames; 250 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 251 sharedElementNames, resultCode, data); 252 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 253 @Override 254 public void onTranslucentConversionComplete(boolean drawComplete) { 255 if (!mIsCanceled) { 256 fadeOutBackground(); 257 } 258 } 259 }, options); 260 startTransition(new Runnable() { 261 @Override 262 public void run() { 263 startExitTransition(); 264 } 265 }); 266 } 267 } 268 stop()269 public void stop() { 270 if (mIsReturning && mActivity != null) { 271 // Override the previous ActivityOptions. We don't want the 272 // activity to have options since we're essentially canceling the 273 // transition and finishing right now. 274 mActivity.convertToTranslucent(null, null); 275 finish(); 276 } 277 } 278 startExitTransition()279 private void startExitTransition() { 280 Transition transition = getExitTransition(); 281 ViewGroup decorView = getDecor(); 282 if (transition != null && decorView != null && mTransitioningViews != null) { 283 setTransitioningViewsVisiblity(View.VISIBLE, false); 284 TransitionManager.beginDelayedTransition(decorView, transition); 285 setTransitioningViewsVisiblity(View.INVISIBLE, false); 286 decorView.invalidate(); 287 } else { 288 transitionStarted(); 289 } 290 } 291 fadeOutBackground()292 private void fadeOutBackground() { 293 if (mBackgroundAnimator == null) { 294 ViewGroup decor = getDecor(); 295 Drawable background; 296 if (decor != null && (background = decor.getBackground()) != null) { 297 background = background.mutate(); 298 getWindow().setBackgroundDrawable(background); 299 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 300 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 301 @Override 302 public void onAnimationEnd(Animator animation) { 303 mBackgroundAnimator = null; 304 if (!mIsCanceled) { 305 mIsBackgroundReady = true; 306 notifyComplete(); 307 } 308 backgroundAnimatorComplete(); 309 } 310 }); 311 mBackgroundAnimator.setDuration(getFadeDuration()); 312 mBackgroundAnimator.start(); 313 } else { 314 backgroundAnimatorComplete(); 315 mIsBackgroundReady = true; 316 } 317 } 318 } 319 getExitTransition()320 private Transition getExitTransition() { 321 Transition viewsTransition = null; 322 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 323 viewsTransition = configureTransition(getViewsTransition(), true); 324 removeExcludedViews(viewsTransition, mTransitioningViews); 325 if (mTransitioningViews.isEmpty()) { 326 viewsTransition = null; 327 } 328 } 329 if (viewsTransition == null) { 330 viewsTransitionComplete(); 331 } else { 332 final ArrayList<View> transitioningViews = mTransitioningViews; 333 viewsTransition.addListener(new ContinueTransitionListener() { 334 @Override 335 public void onTransitionEnd(Transition transition) { 336 viewsTransitionComplete(); 337 if (mIsHidden && transitioningViews != null) { 338 showViews(transitioningViews, true); 339 setTransitioningViewsVisiblity(View.VISIBLE, true); 340 } 341 if (mSharedElementBundle != null) { 342 delayCancel(); 343 } 344 super.onTransitionEnd(transition); 345 } 346 }); 347 } 348 return viewsTransition; 349 } 350 getSharedElementExitTransition()351 private Transition getSharedElementExitTransition() { 352 Transition sharedElementTransition = null; 353 if (!mSharedElements.isEmpty()) { 354 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 355 } 356 if (sharedElementTransition == null) { 357 sharedElementTransitionComplete(); 358 } else { 359 sharedElementTransition.addListener(new ContinueTransitionListener() { 360 @Override 361 public void onTransitionEnd(Transition transition) { 362 sharedElementTransitionComplete(); 363 if (mIsHidden) { 364 showViews(mSharedElements, true); 365 } 366 super.onTransitionEnd(transition); 367 } 368 }); 369 mSharedElements.get(0).invalidate(); 370 } 371 return sharedElementTransition; 372 } 373 beginTransitions()374 private void beginTransitions() { 375 Transition sharedElementTransition = getSharedElementExitTransition(); 376 Transition viewsTransition = getExitTransition(); 377 378 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 379 ViewGroup decorView = getDecor(); 380 if (transition != null && decorView != null) { 381 setGhostVisibility(View.INVISIBLE); 382 scheduleGhostVisibilityChange(View.INVISIBLE); 383 if (viewsTransition != null) { 384 setTransitioningViewsVisiblity(View.VISIBLE, false); 385 } 386 TransitionManager.beginDelayedTransition(decorView, transition); 387 scheduleGhostVisibilityChange(View.VISIBLE); 388 setGhostVisibility(View.VISIBLE); 389 if (viewsTransition != null) { 390 setTransitioningViewsVisiblity(View.INVISIBLE, false); 391 } 392 decorView.invalidate(); 393 } else { 394 transitionStarted(); 395 } 396 } 397 isReadyToNotify()398 protected boolean isReadyToNotify() { 399 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 400 } 401 402 @Override sharedElementTransitionComplete()403 protected void sharedElementTransitionComplete() { 404 mSharedElementBundle = mExitSharedElementBundle == null 405 ? captureSharedElementState() : captureExitSharedElementsState(); 406 super.sharedElementTransitionComplete(); 407 } 408 captureExitSharedElementsState()409 private Bundle captureExitSharedElementsState() { 410 Bundle bundle = new Bundle(); 411 RectF bounds = new RectF(); 412 Matrix matrix = new Matrix(); 413 for (int i = 0; i < mSharedElements.size(); i++) { 414 String name = mSharedElementNames.get(i); 415 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 416 if (sharedElementState != null) { 417 bundle.putBundle(name, sharedElementState); 418 } else { 419 View view = mSharedElements.get(i); 420 captureSharedElementState(view, name, bundle, matrix, bounds); 421 } 422 } 423 return bundle; 424 } 425 426 @Override onTransitionsComplete()427 protected void onTransitionsComplete() { 428 notifyComplete(); 429 } 430 notifyComplete()431 protected void notifyComplete() { 432 if (isReadyToNotify()) { 433 if (!mSharedElementNotified) { 434 mSharedElementNotified = true; 435 delayCancel(); 436 if (mListener == null) { 437 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 438 notifyExitComplete(); 439 } else { 440 final ResultReceiver resultReceiver = mResultReceiver; 441 final Bundle sharedElementBundle = mSharedElementBundle; 442 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, 443 new OnSharedElementsReadyListener() { 444 @Override 445 public void onSharedElementsReady() { 446 resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, 447 sharedElementBundle); 448 notifyExitComplete(); 449 } 450 }); 451 } 452 } else { 453 notifyExitComplete(); 454 } 455 } 456 } 457 notifyExitComplete()458 private void notifyExitComplete() { 459 if (!mExitNotified && isViewsTransitionComplete()) { 460 mExitNotified = true; 461 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 462 mResultReceiver = null; // done talking 463 ViewGroup decorView = getDecor(); 464 if (!mIsReturning && decorView != null) { 465 decorView.suppressLayout(false); 466 } 467 finishIfNecessary(); 468 } 469 } 470 finishIfNecessary()471 private void finishIfNecessary() { 472 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 473 mSharedElementsHidden)) { 474 finish(); 475 } 476 if (!mIsReturning && mExitNotified) { 477 mActivity = null; // don't need it anymore 478 } 479 } 480 finish()481 private void finish() { 482 stopCancel(); 483 if (mActivity != null) { 484 mActivity.mActivityTransitionState.clear(); 485 mActivity.finish(); 486 mActivity.overridePendingTransition(0, 0); 487 mActivity = null; 488 } 489 // Clear the state so that we can't hold any references accidentally and leak memory. 490 clearState(); 491 } 492 493 @Override clearState()494 protected void clearState() { 495 mHandler = null; 496 mSharedElementBundle = null; 497 if (mBackgroundAnimator != null) { 498 mBackgroundAnimator.cancel(); 499 mBackgroundAnimator = null; 500 } 501 mExitSharedElementBundle = null; 502 super.clearState(); 503 } 504 505 @Override moveSharedElementWithParent()506 protected boolean moveSharedElementWithParent() { 507 return !mIsReturning; 508 } 509 510 @Override getViewsTransition()511 protected Transition getViewsTransition() { 512 if (mIsReturning) { 513 return getWindow().getReturnTransition(); 514 } else { 515 return getWindow().getExitTransition(); 516 } 517 } 518 getSharedElementTransition()519 protected Transition getSharedElementTransition() { 520 if (mIsReturning) { 521 return getWindow().getSharedElementReturnTransition(); 522 } else { 523 return getWindow().getSharedElementExitTransition(); 524 } 525 } 526 527 interface HideSharedElementsCallback { hideSharedElements()528 void hideSharedElements(); 529 } 530 } 531