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.TransitionManager; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.ViewTreeObserver; 38 import android.view.Window; 39 40 import java.util.ArrayList; 41 42 /** 43 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 44 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 45 * the reentry of the Scene when coming back from the called Activity. 46 */ 47 class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 48 private static final String TAG = "ExitTransitionCoordinator"; 49 private static final long MAX_WAIT_MS = 1000; 50 51 private Bundle mSharedElementBundle; 52 private boolean mExitNotified; 53 private boolean mSharedElementNotified; 54 private Activity mActivity; 55 private boolean mIsBackgroundReady; 56 private boolean mIsCanceled; 57 private Handler mHandler; 58 private ObjectAnimator mBackgroundAnimator; 59 private boolean mIsHidden; 60 private Bundle mExitSharedElementBundle; 61 private boolean mIsExitStarted; 62 private boolean mSharedElementsHidden; 63 private HideSharedElementsCallback mHideSharedElementsCallback; 64 ExitTransitionCoordinator(Activity activity, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning)65 public ExitTransitionCoordinator(Activity activity, Window window, 66 SharedElementCallback listener, ArrayList<String> names, 67 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { 68 super(window, names, listener, isReturning); 69 viewsReady(mapSharedElements(accepted, mapped)); 70 stripOffscreenViews(); 71 mIsBackgroundReady = !isReturning; 72 mActivity = activity; 73 } 74 setHideSharedElementsCallback(HideSharedElementsCallback callback)75 void setHideSharedElementsCallback(HideSharedElementsCallback callback) { 76 mHideSharedElementsCallback = callback; 77 } 78 79 @Override onReceiveResult(int resultCode, Bundle resultData)80 protected void onReceiveResult(int resultCode, Bundle resultData) { 81 switch (resultCode) { 82 case MSG_SET_REMOTE_RECEIVER: 83 stopCancel(); 84 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 85 if (mIsCanceled) { 86 mResultReceiver.send(MSG_CANCEL, null); 87 mResultReceiver = null; 88 } else { 89 notifyComplete(); 90 } 91 break; 92 case MSG_HIDE_SHARED_ELEMENTS: 93 stopCancel(); 94 if (!mIsCanceled) { 95 hideSharedElements(); 96 } 97 break; 98 case MSG_START_EXIT_TRANSITION: 99 mHandler.removeMessages(MSG_CANCEL); 100 startExit(); 101 break; 102 case MSG_SHARED_ELEMENT_DESTINATION: 103 mExitSharedElementBundle = resultData; 104 sharedElementExitBack(); 105 break; 106 case MSG_CANCEL: 107 mIsCanceled = true; 108 finish(); 109 break; 110 } 111 } 112 stopCancel()113 private void stopCancel() { 114 if (mHandler != null) { 115 mHandler.removeMessages(MSG_CANCEL); 116 } 117 } 118 delayCancel()119 private void delayCancel() { 120 if (mHandler != null) { 121 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 122 } 123 } 124 resetViews()125 public void resetViews() { 126 if (mTransitioningViews != null) { 127 showViews(mTransitioningViews, true); 128 setTransitioningViewsVisiblity(View.VISIBLE, true); 129 } 130 showViews(mSharedElements, true); 131 mIsHidden = true; 132 ViewGroup decorView = getDecor(); 133 if (!mIsReturning && decorView != null) { 134 decorView.suppressLayout(false); 135 } 136 moveSharedElementsFromOverlay(); 137 clearState(); 138 } 139 sharedElementExitBack()140 private void sharedElementExitBack() { 141 final ViewGroup decorView = getDecor(); 142 if (decorView != null) { 143 decorView.suppressLayout(true); 144 } 145 if (decorView != null && mExitSharedElementBundle != null && 146 !mExitSharedElementBundle.isEmpty() && 147 !mSharedElements.isEmpty() && getSharedElementTransition() != null) { 148 startTransition(new Runnable() { 149 public void run() { 150 startSharedElementExit(decorView); 151 } 152 }); 153 } else { 154 sharedElementTransitionComplete(); 155 } 156 } 157 startSharedElementExit(final ViewGroup decorView)158 private void startSharedElementExit(final ViewGroup decorView) { 159 Transition transition = getSharedElementExitTransition(); 160 transition.addListener(new Transition.TransitionListenerAdapter() { 161 @Override 162 public void onTransitionEnd(Transition transition) { 163 transition.removeListener(this); 164 if (isViewsTransitionComplete()) { 165 delayCancel(); 166 } 167 } 168 }); 169 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 170 mSharedElementNames); 171 decorView.getViewTreeObserver() 172 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 173 @Override 174 public boolean onPreDraw() { 175 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 176 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 177 return true; 178 } 179 }); 180 setGhostVisibility(View.INVISIBLE); 181 scheduleGhostVisibilityChange(View.INVISIBLE); 182 if (mListener != null) { 183 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, 184 sharedElementSnapshots); 185 } 186 TransitionManager.beginDelayedTransition(decorView, transition); 187 scheduleGhostVisibilityChange(View.VISIBLE); 188 setGhostVisibility(View.VISIBLE); 189 decorView.invalidate(); 190 } 191 hideSharedElements()192 private void hideSharedElements() { 193 moveSharedElementsFromOverlay(); 194 if (mHideSharedElementsCallback != null) { 195 mHideSharedElementsCallback.hideSharedElements(); 196 } 197 if (!mIsHidden) { 198 hideViews(mSharedElements); 199 } 200 mSharedElementsHidden = true; 201 finishIfNecessary(); 202 } 203 startExit()204 public void startExit() { 205 if (!mIsExitStarted) { 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.BLACK)); 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 } 309 }); 310 mBackgroundAnimator.setDuration(getFadeDuration()); 311 mBackgroundAnimator.start(); 312 } else { 313 mIsBackgroundReady = true; 314 } 315 } 316 } 317 getExitTransition()318 private Transition getExitTransition() { 319 Transition viewsTransition = null; 320 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 321 viewsTransition = configureTransition(getViewsTransition(), true); 322 } 323 if (viewsTransition == null) { 324 viewsTransitionComplete(); 325 } else { 326 final ArrayList<View> transitioningViews = mTransitioningViews; 327 viewsTransition.addListener(new ContinueTransitionListener() { 328 @Override 329 public void onTransitionEnd(Transition transition) { 330 transition.removeListener(this); 331 viewsTransitionComplete(); 332 if (mIsHidden && transitioningViews != null) { 333 showViews(transitioningViews, true); 334 setTransitioningViewsVisiblity(View.VISIBLE, true); 335 } 336 if (mSharedElementBundle != null) { 337 delayCancel(); 338 } 339 super.onTransitionEnd(transition); 340 } 341 }); 342 } 343 return viewsTransition; 344 } 345 getSharedElementExitTransition()346 private Transition getSharedElementExitTransition() { 347 Transition sharedElementTransition = null; 348 if (!mSharedElements.isEmpty()) { 349 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 350 } 351 if (sharedElementTransition == null) { 352 sharedElementTransitionComplete(); 353 } else { 354 sharedElementTransition.addListener(new ContinueTransitionListener() { 355 @Override 356 public void onTransitionEnd(Transition transition) { 357 transition.removeListener(this); 358 sharedElementTransitionComplete(); 359 if (mIsHidden) { 360 showViews(mSharedElements, true); 361 } 362 } 363 }); 364 mSharedElements.get(0).invalidate(); 365 } 366 return sharedElementTransition; 367 } 368 beginTransitions()369 private void beginTransitions() { 370 Transition sharedElementTransition = getSharedElementExitTransition(); 371 Transition viewsTransition = getExitTransition(); 372 373 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 374 ViewGroup decorView = getDecor(); 375 if (transition != null && decorView != null) { 376 setGhostVisibility(View.INVISIBLE); 377 scheduleGhostVisibilityChange(View.INVISIBLE); 378 if (viewsTransition != null) { 379 setTransitioningViewsVisiblity(View.VISIBLE, false); 380 } 381 TransitionManager.beginDelayedTransition(decorView, transition); 382 scheduleGhostVisibilityChange(View.VISIBLE); 383 setGhostVisibility(View.VISIBLE); 384 if (viewsTransition != null) { 385 setTransitioningViewsVisiblity(View.INVISIBLE, false); 386 } 387 decorView.invalidate(); 388 } else { 389 transitionStarted(); 390 } 391 } 392 isReadyToNotify()393 protected boolean isReadyToNotify() { 394 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 395 } 396 397 @Override sharedElementTransitionComplete()398 protected void sharedElementTransitionComplete() { 399 mSharedElementBundle = mExitSharedElementBundle == null 400 ? captureSharedElementState() : captureExitSharedElementsState(); 401 super.sharedElementTransitionComplete(); 402 } 403 captureExitSharedElementsState()404 private Bundle captureExitSharedElementsState() { 405 Bundle bundle = new Bundle(); 406 RectF bounds = new RectF(); 407 Matrix matrix = new Matrix(); 408 for (int i = 0; i < mSharedElements.size(); i++) { 409 String name = mSharedElementNames.get(i); 410 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 411 if (sharedElementState != null) { 412 bundle.putBundle(name, sharedElementState); 413 } else { 414 View view = mSharedElements.get(i); 415 captureSharedElementState(view, name, bundle, matrix, bounds); 416 } 417 } 418 return bundle; 419 } 420 421 @Override onTransitionsComplete()422 protected void onTransitionsComplete() { 423 notifyComplete(); 424 } 425 notifyComplete()426 protected void notifyComplete() { 427 if (isReadyToNotify()) { 428 if (!mSharedElementNotified) { 429 mSharedElementNotified = true; 430 delayCancel(); 431 if (mListener == null) { 432 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 433 notifyExitComplete(); 434 } else { 435 final ResultReceiver resultReceiver = mResultReceiver; 436 final Bundle sharedElementBundle = mSharedElementBundle; 437 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, 438 new OnSharedElementsReadyListener() { 439 @Override 440 public void onSharedElementsReady() { 441 resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, 442 sharedElementBundle); 443 notifyExitComplete(); 444 } 445 }); 446 } 447 } else { 448 notifyExitComplete(); 449 } 450 } 451 } 452 notifyExitComplete()453 private void notifyExitComplete() { 454 if (!mExitNotified && isViewsTransitionComplete()) { 455 mExitNotified = true; 456 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 457 mResultReceiver = null; // done talking 458 ViewGroup decorView = getDecor(); 459 if (!mIsReturning && decorView != null) { 460 decorView.suppressLayout(false); 461 } 462 finishIfNecessary(); 463 } 464 } 465 finishIfNecessary()466 private void finishIfNecessary() { 467 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 468 mSharedElementsHidden)) { 469 finish(); 470 } 471 if (!mIsReturning && mExitNotified) { 472 mActivity = null; // don't need it anymore 473 } 474 } 475 finish()476 private void finish() { 477 stopCancel(); 478 if (mActivity != null) { 479 mActivity.mActivityTransitionState.clear(); 480 mActivity.finish(); 481 mActivity.overridePendingTransition(0, 0); 482 mActivity = null; 483 } 484 // Clear the state so that we can't hold any references accidentally and leak memory. 485 clearState(); 486 } 487 488 @Override clearState()489 protected void clearState() { 490 mHandler = null; 491 mSharedElementBundle = null; 492 if (mBackgroundAnimator != null) { 493 mBackgroundAnimator.cancel(); 494 mBackgroundAnimator = null; 495 } 496 mExitSharedElementBundle = null; 497 super.clearState(); 498 } 499 500 @Override moveSharedElementWithParent()501 protected boolean moveSharedElementWithParent() { 502 return !mIsReturning; 503 } 504 505 @Override getViewsTransition()506 protected Transition getViewsTransition() { 507 if (mIsReturning) { 508 return getWindow().getReturnTransition(); 509 } else { 510 return getWindow().getExitTransition(); 511 } 512 } 513 getSharedElementTransition()514 protected Transition getSharedElementTransition() { 515 if (mIsReturning) { 516 return getWindow().getSharedElementReturnTransition(); 517 } else { 518 return getWindow().getSharedElementExitTransition(); 519 } 520 } 521 522 interface HideSharedElementsCallback { hideSharedElements()523 void hideSharedElements(); 524 } 525 } 526