1 /* 2 * Copyright (C) 2021 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 17 package com.android.wm.shell.transition; 18 19 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; 20 import static android.app.ActivityOptions.ANIM_CUSTOM; 21 import static android.app.ActivityOptions.ANIM_NONE; 22 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; 23 import static android.app.ActivityOptions.ANIM_SCALE_UP; 24 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; 25 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; 26 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; 27 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 28 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 29 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; 30 import static android.view.WindowManager.TRANSIT_CHANGE; 31 import static android.view.WindowManager.TRANSIT_CLOSE; 32 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; 33 import static android.view.WindowManager.TRANSIT_OPEN; 34 import static android.view.WindowManager.TRANSIT_RELAUNCH; 35 import static android.view.WindowManager.TRANSIT_TO_BACK; 36 import static android.view.WindowManager.TRANSIT_TO_FRONT; 37 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; 38 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 39 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 40 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 41 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 42 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 43 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 44 import static android.window.TransitionInfo.isIndependent; 45 46 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; 47 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; 48 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; 49 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; 50 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; 51 52 import android.animation.Animator; 53 import android.animation.AnimatorListenerAdapter; 54 import android.animation.ValueAnimator; 55 import android.annotation.NonNull; 56 import android.annotation.Nullable; 57 import android.content.Context; 58 import android.graphics.Point; 59 import android.graphics.Rect; 60 import android.hardware.HardwareBuffer; 61 import android.os.IBinder; 62 import android.os.SystemProperties; 63 import android.os.UserHandle; 64 import android.util.ArrayMap; 65 import android.view.Choreographer; 66 import android.view.SurfaceControl; 67 import android.view.SurfaceSession; 68 import android.view.WindowManager; 69 import android.view.animation.AlphaAnimation; 70 import android.view.animation.Animation; 71 import android.view.animation.Transformation; 72 import android.window.TransitionInfo; 73 import android.window.TransitionMetrics; 74 import android.window.TransitionRequestInfo; 75 import android.window.WindowContainerToken; 76 import android.window.WindowContainerTransaction; 77 78 import com.android.internal.R; 79 import com.android.internal.annotations.VisibleForTesting; 80 import com.android.internal.policy.AttributeCache; 81 import com.android.internal.policy.TransitionAnimation; 82 import com.android.internal.protolog.common.ProtoLog; 83 import com.android.wm.shell.common.DisplayController; 84 import com.android.wm.shell.common.DisplayLayout; 85 import com.android.wm.shell.common.ShellExecutor; 86 import com.android.wm.shell.common.TransactionPool; 87 import com.android.wm.shell.protolog.ShellProtoLogGroup; 88 import com.android.wm.shell.util.CounterRotator; 89 90 import java.util.ArrayList; 91 92 /** The default handler that handles anything not already handled. */ 93 public class DefaultTransitionHandler implements Transitions.TransitionHandler { 94 private static final int MAX_ANIMATION_DURATION = 3000; 95 96 /** 97 * Restrict ability of activities overriding transition animation in a way such that 98 * an activity can do it only when the transition happens within a same task. 99 * 100 * @see android.app.Activity#overridePendingTransition(int, int) 101 */ 102 private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY = 103 "persist.wm.disable_custom_task_animation"; 104 105 /** 106 * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY 107 */ 108 static boolean sDisableCustomTaskAnimationProperty = 109 SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true); 110 111 private final TransactionPool mTransactionPool; 112 private final DisplayController mDisplayController; 113 private final Context mContext; 114 private final ShellExecutor mMainExecutor; 115 private final ShellExecutor mAnimExecutor; 116 private final TransitionAnimation mTransitionAnimation; 117 118 private final SurfaceSession mSurfaceSession = new SurfaceSession(); 119 120 /** Keeps track of the currently-running animations associated with each transition. */ 121 private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); 122 123 private final Rect mInsets = new Rect(0, 0, 0, 0); 124 private float mTransitionAnimationScaleSetting = 1.0f; 125 126 private final int mCurrentUserId; 127 128 private ScreenRotationAnimation mRotationAnimation; 129 DefaultTransitionHandler(@onNull DisplayController displayController, @NonNull TransactionPool transactionPool, Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor)130 DefaultTransitionHandler(@NonNull DisplayController displayController, 131 @NonNull TransactionPool transactionPool, Context context, 132 @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { 133 mDisplayController = displayController; 134 mTransactionPool = transactionPool; 135 mContext = context; 136 mMainExecutor = mainExecutor; 137 mAnimExecutor = animExecutor; 138 mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG); 139 mCurrentUserId = UserHandle.myUserId(); 140 141 AttributeCache.init(context); 142 } 143 144 @VisibleForTesting isRotationSeamless(@onNull TransitionInfo info, DisplayController displayController)145 static boolean isRotationSeamless(@NonNull TransitionInfo info, 146 DisplayController displayController) { 147 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 148 "Display is changing, check if it should be seamless."); 149 boolean checkedDisplayLayout = false; 150 boolean hasTask = false; 151 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 152 final TransitionInfo.Change change = info.getChanges().get(i); 153 154 // Only look at changing things. showing/hiding don't need to rotate. 155 if (change.getMode() != TRANSIT_CHANGE) continue; 156 157 // This container isn't rotating, so we can ignore it. 158 if (change.getEndRotation() == change.getStartRotation()) continue; 159 160 if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) { 161 // In the presence of System Alert windows we can not seamlessly rotate. 162 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { 163 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 164 " display has system alert windows, so not seamless."); 165 return false; 166 } 167 } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { 168 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { 169 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 170 " wallpaper is participating but isn't seamless."); 171 return false; 172 } 173 } else if (change.getTaskInfo() != null) { 174 hasTask = true; 175 // We only enable seamless rotation if all the visible task windows requested it. 176 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { 177 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 178 " task %s isn't requesting seamless, so not seamless.", 179 change.getTaskInfo().taskId); 180 return false; 181 } 182 183 // This is the only way to get display-id currently, so we will check display 184 // capabilities here 185 if (!checkedDisplayLayout) { 186 // only need to check display once. 187 checkedDisplayLayout = true; 188 final DisplayLayout displayLayout = displayController.getDisplayLayout( 189 change.getTaskInfo().displayId); 190 // For the upside down rotation we don't rotate seamlessly as the navigation 191 // bar moves position. Note most apps (using orientation:sensor or user as 192 // opposed to fullSensor) will not enter the reverse portrait orientation, so 193 // actually the orientation won't change at all. 194 int upsideDownRotation = displayLayout.getUpsideDownRotation(); 195 if (change.getStartRotation() == upsideDownRotation 196 || change.getEndRotation() == upsideDownRotation) { 197 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 198 " rotation involves upside-down portrait, so not seamless."); 199 return false; 200 } 201 202 // If the navigation bar can't change sides, then it will jump when we change 203 // orientations and we don't rotate seamlessly - unless that is allowed, eg. 204 // with gesture navigation where the navbar is low-profile enough that this 205 // isn't very noticeable. 206 if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving() 207 && (!(displayLayout.navigationBarCanMove() 208 && (change.getStartAbsBounds().width() 209 != change.getStartAbsBounds().height())))) { 210 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 211 " nav bar changes sides, so not seamless."); 212 return false; 213 } 214 } 215 } 216 } 217 218 // ROTATION_ANIMATION_SEAMLESS can only be requested by task. 219 if (hasTask) { 220 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless."); 221 return true; 222 } 223 return false; 224 } 225 226 /** 227 * Gets the rotation animation for the topmost task. Assumes that seamless is checked 228 * elsewhere, so it will default SEAMLESS to ROTATE. 229 */ getRotationAnimation(@onNull TransitionInfo info)230 private int getRotationAnimation(@NonNull TransitionInfo info) { 231 // Traverse in top-to-bottom order so that the first task is top-most 232 for (int i = 0; i < info.getChanges().size(); ++i) { 233 final TransitionInfo.Change change = info.getChanges().get(i); 234 235 // Only look at changing things. showing/hiding don't need to rotate. 236 if (change.getMode() != TRANSIT_CHANGE) continue; 237 238 // This container isn't rotating, so we can ignore it. 239 if (change.getEndRotation() == change.getStartRotation()) continue; 240 241 if (change.getTaskInfo() != null) { 242 final int anim = change.getRotationAnimation(); 243 if (anim == ROTATION_ANIMATION_UNSPECIFIED 244 // Fallback animation for seamless should also be default. 245 || anim == ROTATION_ANIMATION_SEAMLESS) { 246 return ROTATION_ANIMATION_ROTATE; 247 } 248 return anim; 249 } 250 } 251 return ROTATION_ANIMATION_ROTATE; 252 } 253 254 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)255 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 256 @NonNull SurfaceControl.Transaction startTransaction, 257 @NonNull SurfaceControl.Transaction finishTransaction, 258 @NonNull Transitions.TransitionFinishCallback finishCallback) { 259 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 260 "start default transition animation, info = %s", info); 261 // If keyguard goes away, we should loadKeyguardExitAnimation. Otherwise this just 262 // immediately finishes since there is no animation for screen-wake. 263 if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) { 264 startTransaction.apply(); 265 finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); 266 return true; 267 } 268 269 if (mAnimations.containsKey(transition)) { 270 throw new IllegalStateException("Got a duplicate startAnimation call for " 271 + transition); 272 } 273 final ArrayList<Animator> animations = new ArrayList<>(); 274 mAnimations.put(transition, animations); 275 276 final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>(); 277 278 final Runnable onAnimFinish = () -> { 279 if (!animations.isEmpty()) return; 280 281 for (int i = 0; i < counterRotators.size(); ++i) { 282 counterRotators.valueAt(i).cleanUp(info.getRootLeash()); 283 } 284 counterRotators.clear(); 285 286 if (mRotationAnimation != null) { 287 mRotationAnimation.kill(); 288 mRotationAnimation = null; 289 } 290 291 mAnimations.remove(transition); 292 finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); 293 }; 294 295 final int wallpaperTransit = getWallpaperTransitType(info); 296 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 297 final TransitionInfo.Change change = info.getChanges().get(i); 298 299 if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { 300 int rotateDelta = change.getEndRotation() - change.getStartRotation(); 301 int displayW = change.getEndAbsBounds().width(); 302 int displayH = change.getEndAbsBounds().height(); 303 if (info.getType() == TRANSIT_CHANGE) { 304 boolean isSeamless = isRotationSeamless(info, mDisplayController); 305 final int anim = getRotationAnimation(info); 306 if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) { 307 mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession, 308 mTransactionPool, startTransaction, change, info.getRootLeash()); 309 mRotationAnimation.startAnimation(animations, onAnimFinish, 310 mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor); 311 continue; 312 } 313 } else { 314 // opening/closing an app into a new orientation. Counter-rotate all 315 // "going-away" things since they are still in the old orientation. 316 for (int j = info.getChanges().size() - 1; j >= 0; --j) { 317 final TransitionInfo.Change innerChange = info.getChanges().get(j); 318 if (!Transitions.isClosingType(innerChange.getMode()) 319 || !isIndependent(innerChange, info) 320 || innerChange.getParent() == null) { 321 continue; 322 } 323 CounterRotator crot = counterRotators.get(innerChange.getParent()); 324 if (crot == null) { 325 crot = new CounterRotator(); 326 crot.setup(startTransaction, 327 info.getChange(innerChange.getParent()).getLeash(), 328 rotateDelta, displayW, displayH); 329 if (crot.getSurface() != null) { 330 int layer = info.getChanges().size() - j; 331 startTransaction.setLayer(crot.getSurface(), layer); 332 } 333 counterRotators.put(innerChange.getParent(), crot); 334 } 335 crot.addChild(startTransaction, innerChange.getLeash()); 336 } 337 } 338 } 339 340 if (change.getMode() == TRANSIT_CHANGE) { 341 // No default animation for this, so just update bounds/position. 342 startTransaction.setPosition(change.getLeash(), 343 change.getEndAbsBounds().left - change.getEndRelOffset().x, 344 change.getEndAbsBounds().top - change.getEndRelOffset().y); 345 if (change.getTaskInfo() != null) { 346 // Skip non-tasks since those usually have null bounds. 347 startTransaction.setWindowCrop(change.getLeash(), 348 change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); 349 } 350 } 351 352 // Don't animate anything that isn't independent. 353 if (!TransitionInfo.isIndependent(change, info)) continue; 354 355 Animation a = loadAnimation(info, change, wallpaperTransit); 356 if (a != null) { 357 startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, 358 mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */); 359 360 if (info.getAnimationOptions() != null) { 361 attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions()); 362 } 363 } 364 } 365 startTransaction.apply(); 366 TransitionMetrics.getInstance().reportAnimationStart(transition); 367 // run finish now in-case there are no animations 368 onAnimFinish.run(); 369 return true; 370 } 371 372 @Nullable 373 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)374 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 375 @NonNull TransitionRequestInfo request) { 376 return null; 377 } 378 379 @Override setAnimScaleSetting(float scale)380 public void setAnimScaleSetting(float scale) { 381 mTransitionAnimationScaleSetting = scale; 382 } 383 384 @Nullable loadAnimation(TransitionInfo info, TransitionInfo.Change change, int wallpaperTransit)385 private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change, 386 int wallpaperTransit) { 387 Animation a = null; 388 389 final int type = info.getType(); 390 final int flags = info.getFlags(); 391 final int changeMode = change.getMode(); 392 final int changeFlags = change.getFlags(); 393 final boolean isOpeningType = Transitions.isOpeningType(type); 394 final boolean enter = Transitions.isOpeningType(changeMode); 395 final boolean isTask = change.getTaskInfo() != null; 396 final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); 397 final int overrideType = options != null ? options.getType() : ANIM_NONE; 398 final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true; 399 400 if (info.isKeyguardGoingAway()) { 401 a = mTransitionAnimation.loadKeyguardExitAnimation(flags, 402 (changeFlags & FLAG_SHOW_WALLPAPER) != 0); 403 } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) { 404 a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(); 405 } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { 406 if (isOpeningType) { 407 a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter); 408 } else { 409 a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter); 410 } 411 } else if (changeMode == TRANSIT_CHANGE) { 412 // In the absence of a specific adapter, we just want to keep everything stationary. 413 a = new AlphaAnimation(1.f, 1.f); 414 a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION); 415 } else if (type == TRANSIT_RELAUNCH) { 416 a = mTransitionAnimation.createRelaunchAnimation( 417 change.getEndAbsBounds(), mInsets, change.getEndAbsBounds()); 418 } else if (overrideType == ANIM_CUSTOM 419 && (canCustomContainer || options.getOverrideTaskTransition())) { 420 a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter 421 ? options.getEnterResId() : options.getExitResId()); 422 } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) { 423 a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); 424 } else if (overrideType == ANIM_CLIP_REVEAL) { 425 a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter, 426 change.getEndAbsBounds(), change.getEndAbsBounds(), 427 options.getTransitionBounds()); 428 } else if (overrideType == ANIM_SCALE_UP) { 429 a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter, 430 change.getEndAbsBounds(), options.getTransitionBounds()); 431 } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP 432 || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) { 433 final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP; 434 a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp, 435 change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(), 436 options.getTransitionBounds()); 437 } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { 438 // This received a transferred starting window, so don't animate 439 return null; 440 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { 441 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 442 ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation 443 : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation); 444 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { 445 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 446 ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation 447 : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation); 448 } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { 449 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 450 ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation 451 : R.styleable.WindowAnimation_wallpaperOpenExitAnimation); 452 } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { 453 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 454 ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation 455 : R.styleable.WindowAnimation_wallpaperCloseExitAnimation); 456 } else if (type == TRANSIT_OPEN) { 457 if (isTask) { 458 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 459 ? R.styleable.WindowAnimation_taskOpenEnterAnimation 460 : R.styleable.WindowAnimation_taskOpenExitAnimation); 461 } else { 462 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { 463 a = mTransitionAnimation.loadDefaultAnimationRes( 464 R.anim.activity_translucent_open_enter); 465 } else { 466 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 467 ? R.styleable.WindowAnimation_activityOpenEnterAnimation 468 : R.styleable.WindowAnimation_activityOpenExitAnimation); 469 } 470 } 471 } else if (type == TRANSIT_TO_FRONT) { 472 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 473 ? R.styleable.WindowAnimation_taskToFrontEnterAnimation 474 : R.styleable.WindowAnimation_taskToFrontExitAnimation); 475 } else if (type == TRANSIT_CLOSE) { 476 if (isTask) { 477 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 478 ? R.styleable.WindowAnimation_taskCloseEnterAnimation 479 : R.styleable.WindowAnimation_taskCloseExitAnimation); 480 } else { 481 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { 482 a = mTransitionAnimation.loadDefaultAnimationRes( 483 R.anim.activity_translucent_close_exit); 484 } else { 485 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 486 ? R.styleable.WindowAnimation_activityCloseEnterAnimation 487 : R.styleable.WindowAnimation_activityCloseExitAnimation); 488 } 489 } 490 } else if (type == TRANSIT_TO_BACK) { 491 a = mTransitionAnimation.loadDefaultAnimationAttr(enter 492 ? R.styleable.WindowAnimation_taskToBackEnterAnimation 493 : R.styleable.WindowAnimation_taskToBackExitAnimation); 494 } 495 496 if (a != null) { 497 if (!a.isInitialized()) { 498 Rect end = change.getEndAbsBounds(); 499 a.initialize(end.width(), end.height(), end.width(), end.height()); 500 } 501 a.restrictDuration(MAX_ANIMATION_DURATION); 502 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 503 } 504 return a; 505 } 506 startSurfaceAnimation(@onNull ArrayList<Animator> animations, @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor, @Nullable Point position)507 static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations, 508 @NonNull Animation anim, @NonNull SurfaceControl leash, 509 @NonNull Runnable finishCallback, @NonNull TransactionPool pool, 510 @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor, 511 @Nullable Point position) { 512 final SurfaceControl.Transaction transaction = pool.acquire(); 513 final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); 514 final Transformation transformation = new Transformation(); 515 final float[] matrix = new float[9]; 516 // Animation length is already expected to be scaled. 517 va.overrideDurationScale(1.0f); 518 va.setDuration(anim.computeDurationHint()); 519 va.addUpdateListener(animation -> { 520 final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); 521 522 applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, 523 position); 524 }); 525 526 final Runnable finisher = () -> { 527 applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, 528 position); 529 530 pool.release(transaction); 531 mainExecutor.execute(() -> { 532 animations.remove(va); 533 finishCallback.run(); 534 }); 535 }; 536 va.addListener(new AnimatorListenerAdapter() { 537 @Override 538 public void onAnimationEnd(Animator animation) { 539 finisher.run(); 540 } 541 542 @Override 543 public void onAnimationCancel(Animator animation) { 544 finisher.run(); 545 } 546 }); 547 animations.add(va); 548 animExecutor.execute(va::start); 549 } 550 attachThumbnail(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options)551 private void attachThumbnail(@NonNull ArrayList<Animator> animations, 552 @NonNull Runnable finishCallback, TransitionInfo.Change change, 553 TransitionInfo.AnimationOptions options) { 554 final boolean isTask = change.getTaskInfo() != null; 555 final boolean isOpen = Transitions.isOpeningType(change.getMode()); 556 final boolean isClose = Transitions.isClosingType(change.getMode()); 557 if (isOpen) { 558 if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) { 559 attachCrossProfileThunmbnailAnimation(animations, finishCallback, change); 560 } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) { 561 attachThumbnailAnimation(animations, finishCallback, change, options); 562 } 563 } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) { 564 attachThumbnailAnimation(animations, finishCallback, change, options); 565 } 566 } 567 attachCrossProfileThunmbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change)568 private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations, 569 @NonNull Runnable finishCallback, TransitionInfo.Change change) { 570 final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId 571 ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge; 572 final Rect bounds = change.getEndAbsBounds(); 573 final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail( 574 thumbnailDrawableRes, bounds); 575 if (thumbnail == null) { 576 return; 577 } 578 579 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 580 final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession, 581 change.getLeash(), thumbnail, transaction); 582 final Animation a = 583 mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds); 584 if (a == null) { 585 return; 586 } 587 588 final Runnable finisher = () -> { 589 wt.destroy(transaction); 590 mTransactionPool.release(transaction); 591 592 finishCallback.run(); 593 }; 594 a.restrictDuration(MAX_ANIMATION_DURATION); 595 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 596 startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, 597 mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top)); 598 } 599 attachThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options)600 private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, 601 @NonNull Runnable finishCallback, TransitionInfo.Change change, 602 TransitionInfo.AnimationOptions options) { 603 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 604 final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession, 605 change.getLeash(), options.getThumbnail(), transaction); 606 final Rect bounds = change.getEndAbsBounds(); 607 final int orientation = mContext.getResources().getConfiguration().orientation; 608 final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds, 609 mInsets, options.getThumbnail(), orientation, null /* startRect */, 610 options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP); 611 612 final Runnable finisher = () -> { 613 wt.destroy(transaction); 614 mTransactionPool.release(transaction); 615 616 finishCallback.run(); 617 }; 618 a.restrictDuration(MAX_ANIMATION_DURATION); 619 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 620 startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, 621 mMainExecutor, mAnimExecutor, null /* position */); 622 } 623 getWallpaperTransitType(TransitionInfo info)624 private static int getWallpaperTransitType(TransitionInfo info) { 625 boolean hasOpenWallpaper = false; 626 boolean hasCloseWallpaper = false; 627 628 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 629 final TransitionInfo.Change change = info.getChanges().get(i); 630 if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { 631 if (Transitions.isOpeningType(change.getMode())) { 632 hasOpenWallpaper = true; 633 } else if (Transitions.isClosingType(change.getMode())) { 634 hasCloseWallpaper = true; 635 } 636 } 637 } 638 639 if (hasOpenWallpaper && hasCloseWallpaper) { 640 return Transitions.isOpeningType(info.getType()) 641 ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE; 642 } else if (hasOpenWallpaper) { 643 return WALLPAPER_TRANSITION_OPEN; 644 } else if (hasCloseWallpaper) { 645 return WALLPAPER_TRANSITION_CLOSE; 646 } else { 647 return WALLPAPER_TRANSITION_NONE; 648 } 649 } 650 applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix, Point position)651 private static void applyTransformation(long time, SurfaceControl.Transaction t, 652 SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix, 653 Point position) { 654 anim.getTransformation(time, transformation); 655 if (position != null) { 656 transformation.getMatrix().postTranslate(position.x, position.y); 657 } 658 t.setMatrix(leash, transformation.getMatrix(), matrix); 659 t.setAlpha(leash, transformation.getAlpha()); 660 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 661 t.apply(); 662 } 663 } 664