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