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