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.view.WindowManager.TRANSIT_CHANGE; 20 import static android.view.WindowManager.TRANSIT_CLOSE; 21 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; 22 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 23 import static android.view.WindowManager.TRANSIT_OPEN; 24 import static android.view.WindowManager.TRANSIT_TO_BACK; 25 import static android.view.WindowManager.TRANSIT_TO_FRONT; 26 import static android.view.WindowManager.fixScale; 27 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; 28 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 29 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 30 31 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; 32 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; 33 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.app.ActivityTaskManager; 37 import android.app.IApplicationThread; 38 import android.content.ContentResolver; 39 import android.content.Context; 40 import android.database.ContentObserver; 41 import android.os.Handler; 42 import android.os.IBinder; 43 import android.os.RemoteException; 44 import android.os.SystemProperties; 45 import android.provider.Settings; 46 import android.util.Log; 47 import android.view.SurfaceControl; 48 import android.view.WindowManager; 49 import android.window.ITransitionPlayer; 50 import android.window.RemoteTransition; 51 import android.window.TransitionFilter; 52 import android.window.TransitionInfo; 53 import android.window.TransitionMetrics; 54 import android.window.TransitionRequestInfo; 55 import android.window.WindowContainerTransaction; 56 import android.window.WindowContainerTransactionCallback; 57 import android.window.WindowOrganizer; 58 59 import androidx.annotation.BinderThread; 60 61 import com.android.internal.R; 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.protolog.common.ProtoLog; 64 import com.android.wm.shell.common.DisplayController; 65 import com.android.wm.shell.common.ExternalInterfaceBinder; 66 import com.android.wm.shell.common.RemoteCallable; 67 import com.android.wm.shell.common.ShellExecutor; 68 import com.android.wm.shell.common.TransactionPool; 69 import com.android.wm.shell.common.annotations.ExternalThread; 70 import com.android.wm.shell.protolog.ShellProtoLogGroup; 71 import com.android.wm.shell.sysui.ShellController; 72 import com.android.wm.shell.sysui.ShellInit; 73 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 77 /** Plays transition animations */ 78 public class Transitions implements RemoteCallable<Transitions> { 79 static final String TAG = "ShellTransitions"; 80 81 /** Set to {@code true} to enable shell transitions. */ 82 public static final boolean ENABLE_SHELL_TRANSITIONS = 83 SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); 84 public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS 85 && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); 86 87 /** Transition type for exiting PIP via the Shell, via pressing the expand button. */ 88 public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1; 89 90 public static final int TRANSIT_EXIT_PIP_TO_SPLIT = TRANSIT_FIRST_CUSTOM + 2; 91 92 /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */ 93 public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 3; 94 95 /** Transition type for launching 2 tasks simultaneously. */ 96 public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 4; 97 98 /** Transition type for entering split by opening an app into side-stage. */ 99 public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5; 100 101 /** Transition type for dismissing split-screen via dragging the divider off the screen. */ 102 public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 6; 103 104 /** Transition type for dismissing split-screen. */ 105 public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7; 106 107 /** Transition type for freeform to maximize transition. */ 108 public static final int TRANSIT_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 8; 109 110 /** Transition type for maximize to freeform transition. */ 111 public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9; 112 113 private final WindowOrganizer mOrganizer; 114 private final Context mContext; 115 private final ShellExecutor mMainExecutor; 116 private final ShellExecutor mAnimExecutor; 117 private final TransitionPlayerImpl mPlayerImpl; 118 private final DefaultTransitionHandler mDefaultTransitionHandler; 119 private final RemoteTransitionHandler mRemoteTransitionHandler; 120 private final DisplayController mDisplayController; 121 private final ShellController mShellController; 122 private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); 123 124 private boolean mIsRegistered = false; 125 126 /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ 127 private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); 128 129 private final ArrayList<TransitionObserver> mObservers = new ArrayList<>(); 130 131 /** List of {@link Runnable} instances to run when the last active transition has finished. */ 132 private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>(); 133 134 private float mTransitionAnimationScaleSetting = 1.0f; 135 136 private static final class ActiveTransition { 137 IBinder mToken; 138 TransitionHandler mHandler; 139 boolean mMerged; 140 boolean mAborted; 141 TransitionInfo mInfo; 142 SurfaceControl.Transaction mStartT; 143 SurfaceControl.Transaction mFinishT; 144 } 145 146 /** Keeps track of currently playing transitions in the order of receipt. */ 147 private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>(); 148 Transitions(@onNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor)149 public Transitions(@NonNull Context context, 150 @NonNull ShellInit shellInit, 151 @NonNull ShellController shellController, 152 @NonNull WindowOrganizer organizer, 153 @NonNull TransactionPool pool, 154 @NonNull DisplayController displayController, 155 @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, 156 @NonNull ShellExecutor animExecutor) { 157 mOrganizer = organizer; 158 mContext = context; 159 mMainExecutor = mainExecutor; 160 mAnimExecutor = animExecutor; 161 mDisplayController = displayController; 162 mPlayerImpl = new TransitionPlayerImpl(); 163 mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit, 164 displayController, pool, mainExecutor, mainHandler, animExecutor); 165 mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor); 166 mShellController = shellController; 167 // The very last handler (0 in the list) should be the default one. 168 mHandlers.add(mDefaultTransitionHandler); 169 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default"); 170 // Next lowest priority is remote transitions. 171 mHandlers.add(mRemoteTransitionHandler); 172 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); 173 shellInit.addInitCallback(this::onInit, this); 174 } 175 onInit()176 private void onInit() { 177 mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS, 178 this::createExternalInterface, this); 179 180 ContentResolver resolver = mContext.getContentResolver(); 181 mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); 182 dispatchAnimScaleSetting(mTransitionAnimationScaleSetting); 183 184 resolver.registerContentObserver( 185 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false, 186 new SettingsObserver()); 187 188 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 189 mIsRegistered = true; 190 // Register this transition handler with Core 191 try { 192 mOrganizer.registerTransitionPlayer(mPlayerImpl); 193 } catch (RuntimeException e) { 194 mIsRegistered = false; 195 throw e; 196 } 197 // Pre-load the instance. 198 TransitionMetrics.getInstance(); 199 } 200 } 201 isRegistered()202 public boolean isRegistered() { 203 return mIsRegistered; 204 } 205 getTransitionAnimationScaleSetting()206 private float getTransitionAnimationScaleSetting() { 207 return fixScale(Settings.Global.getFloat(mContext.getContentResolver(), 208 Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat( 209 R.dimen.config_appTransitionAnimationDurationScaleDefault))); 210 } 211 asRemoteTransitions()212 public ShellTransitions asRemoteTransitions() { 213 return mImpl; 214 } 215 createExternalInterface()216 private ExternalInterfaceBinder createExternalInterface() { 217 return new IShellTransitionsImpl(this); 218 } 219 220 @Override getContext()221 public Context getContext() { 222 return mContext; 223 } 224 225 @Override getRemoteCallExecutor()226 public ShellExecutor getRemoteCallExecutor() { 227 return mMainExecutor; 228 } 229 dispatchAnimScaleSetting(float scale)230 private void dispatchAnimScaleSetting(float scale) { 231 for (int i = mHandlers.size() - 1; i >= 0; --i) { 232 mHandlers.get(i).setAnimScaleSetting(scale); 233 } 234 } 235 236 /** 237 * Adds a handler candidate. 238 * @see TransitionHandler 239 */ addHandler(@onNull TransitionHandler handler)240 public void addHandler(@NonNull TransitionHandler handler) { 241 if (mHandlers.isEmpty()) { 242 throw new RuntimeException("Unexpected handler added prior to initialization, please " 243 + "use ShellInit callbacks to ensure proper ordering"); 244 } 245 mHandlers.add(handler); 246 // Set initial scale settings. 247 handler.setAnimScaleSetting(mTransitionAnimationScaleSetting); 248 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s", 249 handler.getClass().getSimpleName()); 250 } 251 getMainExecutor()252 public ShellExecutor getMainExecutor() { 253 return mMainExecutor; 254 } 255 getAnimExecutor()256 public ShellExecutor getAnimExecutor() { 257 return mAnimExecutor; 258 } 259 260 /** Only use this in tests. This is used to avoid running animations during tests. */ 261 @VisibleForTesting replaceDefaultHandlerForTest(TransitionHandler handler)262 void replaceDefaultHandlerForTest(TransitionHandler handler) { 263 mHandlers.set(0, handler); 264 } 265 266 /** Register a remote transition to be used when `filter` matches an incoming transition */ registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)267 public void registerRemote(@NonNull TransitionFilter filter, 268 @NonNull RemoteTransition remoteTransition) { 269 mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 270 } 271 272 /** Unregisters a remote transition and all associated filters */ unregisterRemote(@onNull RemoteTransition remoteTransition)273 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 274 mRemoteTransitionHandler.removeFiltered(remoteTransition); 275 } 276 277 /** Registers an observer on the lifecycle of transitions. */ registerObserver(@onNull TransitionObserver observer)278 public void registerObserver(@NonNull TransitionObserver observer) { 279 mObservers.add(observer); 280 } 281 282 /** Unregisters the observer. */ unregisterObserver(@onNull TransitionObserver observer)283 public void unregisterObserver(@NonNull TransitionObserver observer) { 284 mObservers.remove(observer); 285 } 286 287 /** Boosts the process priority of remote animation player. */ setRunningRemoteTransitionDelegate(IApplicationThread appThread)288 public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) { 289 if (appThread == null) return; 290 try { 291 ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread); 292 } catch (SecurityException e) { 293 Log.e(TAG, "Unable to boost animation process. This should only happen" 294 + " during unit tests"); 295 } catch (RemoteException e) { 296 e.rethrowFromSystemServer(); 297 } 298 } 299 300 /** 301 * Runs the given {@code runnable} when the last active transition has finished, or immediately 302 * if there are currently no active transitions. 303 * 304 * <p>This method should be called on the Shell main-thread, where the given {@code runnable} 305 * will be executed when the last active transition is finished. 306 */ runOnIdle(Runnable runnable)307 public void runOnIdle(Runnable runnable) { 308 if (mActiveTransitions.isEmpty()) { 309 runnable.run(); 310 } else { 311 mRunWhenIdleQueue.add(runnable); 312 } 313 } 314 315 /** @return true if the transition was triggered by opening something vs closing something */ isOpeningType(@indowManager.TransitionType int type)316 public static boolean isOpeningType(@WindowManager.TransitionType int type) { 317 return type == TRANSIT_OPEN 318 || type == TRANSIT_TO_FRONT 319 || type == TRANSIT_KEYGUARD_GOING_AWAY; 320 } 321 322 /** @return true if the transition was triggered by closing something vs opening something */ isClosingType(@indowManager.TransitionType int type)323 public static boolean isClosingType(@WindowManager.TransitionType int type) { 324 return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; 325 } 326 327 /** 328 * Sets up visibility/alpha/transforms to resemble the starting state of an animation. 329 */ setupStartState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)330 private static void setupStartState(@NonNull TransitionInfo info, 331 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 332 boolean isOpening = isOpeningType(info.getType()); 333 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 334 final TransitionInfo.Change change = info.getChanges().get(i); 335 if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { 336 // Currently system windows are controlled by WindowState, so don't change their 337 // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly. 338 // This includes Wallpaper (always z-ordered at bottom) and IME (associated with 339 // app), because there may not be a transition associated with their visibility 340 // changes, and currently they don't need transition animation. 341 continue; 342 } 343 final SurfaceControl leash = change.getLeash(); 344 final int mode = info.getChanges().get(i).getMode(); 345 346 if (mode == TRANSIT_TO_FRONT 347 && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height() 348 || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) { 349 // When the window is moved to front with a different size, make sure the crop is 350 // updated to prevent it from using the old crop. 351 t.setWindowCrop(leash, change.getEndAbsBounds().width(), 352 change.getEndAbsBounds().height()); 353 } 354 355 // Don't move anything that isn't independent within its parents 356 if (!TransitionInfo.isIndependent(change, info)) { 357 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { 358 t.show(leash); 359 t.setMatrix(leash, 1, 0, 0, 1); 360 t.setAlpha(leash, 1.f); 361 t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); 362 } 363 continue; 364 } 365 366 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { 367 t.show(leash); 368 t.setMatrix(leash, 1, 0, 0, 1); 369 if (isOpening 370 // If this is a transferred starting window, we want it immediately visible. 371 && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { 372 t.setAlpha(leash, 0.f); 373 } 374 } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { 375 finishT.hide(leash); 376 } 377 } 378 } 379 380 /** 381 * Reparents all participants into a shared parent and orders them based on: the global transit 382 * type, their transit mode, and their destination z-order. 383 */ setupAnimHierarchy(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)384 private static void setupAnimHierarchy(@NonNull TransitionInfo info, 385 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 386 boolean isOpening = isOpeningType(info.getType()); 387 if (info.getRootLeash().isValid()) { 388 t.show(info.getRootLeash()); 389 } 390 final int numChanges = info.getChanges().size(); 391 // Put animating stuff above this line and put static stuff below it. 392 final int zSplitLine = numChanges + 1; 393 // changes should be ordered top-to-bottom in z 394 for (int i = numChanges - 1; i >= 0; --i) { 395 final TransitionInfo.Change change = info.getChanges().get(i); 396 final SurfaceControl leash = change.getLeash(); 397 final int mode = change.getMode(); 398 399 // Don't reparent anything that isn't independent within its parents 400 if (!TransitionInfo.isIndependent(change, info)) { 401 continue; 402 } 403 404 boolean hasParent = change.getParent() != null; 405 406 if (!hasParent) { 407 t.reparent(leash, info.getRootLeash()); 408 t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, 409 change.getStartAbsBounds().top - info.getRootOffset().y); 410 } 411 final int layer; 412 // Put all the OPEN/SHOW on top 413 if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { 414 // Wallpaper is always at the bottom. 415 layer = -zSplitLine; 416 } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { 417 if (isOpening) { 418 // put on top 419 layer = zSplitLine + numChanges - i; 420 } else { 421 // put on bottom 422 layer = zSplitLine - i; 423 } 424 } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { 425 if (isOpening) { 426 // put on bottom and leave visible 427 layer = zSplitLine - i; 428 } else { 429 // put on top 430 layer = zSplitLine + numChanges - i; 431 } 432 } else { // CHANGE or other 433 layer = zSplitLine + numChanges - i; 434 } 435 t.setLayer(leash, layer); 436 } 437 } 438 findActiveTransition(IBinder token)439 private int findActiveTransition(IBinder token) { 440 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 441 if (mActiveTransitions.get(i).mToken == token) return i; 442 } 443 return -1; 444 } 445 446 @VisibleForTesting onTransitionReady(@onNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)447 void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, 448 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 449 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", 450 transitionToken, info); 451 final int activeIdx = findActiveTransition(transitionToken); 452 if (activeIdx < 0) { 453 throw new IllegalStateException("Got transitionReady for non-active transition " 454 + transitionToken + ". expecting one of " 455 + Arrays.toString(mActiveTransitions.stream().map( 456 activeTransition -> activeTransition.mToken).toArray())); 457 } 458 459 for (int i = 0; i < mObservers.size(); ++i) { 460 mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT); 461 } 462 463 if (!info.getRootLeash().isValid()) { 464 // Invalid root-leash implies that the transition is empty/no-op, so just do 465 // housekeeping and return. 466 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s", 467 transitionToken, info); 468 t.apply(); 469 finishT.apply(); 470 onAbort(transitionToken); 471 return; 472 } 473 474 final int changeSize = info.getChanges().size(); 475 boolean taskChange = false; 476 boolean transferStartingWindow = false; 477 boolean allOccluded = changeSize > 0; 478 for (int i = changeSize - 1; i >= 0; --i) { 479 final TransitionInfo.Change change = info.getChanges().get(i); 480 taskChange |= change.getTaskInfo() != null; 481 transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); 482 if (!change.hasFlags(FLAG_IS_OCCLUDED)) { 483 allOccluded = false; 484 } 485 } 486 // There does not need animation when: 487 // A. Transfer starting window. Apply transfer starting window directly if there is no other 488 // task change. Since this is an activity->activity situation, we can detect it by selecting 489 // transitions with only 2 changes where neither are tasks and one is a starting-window 490 // recipient. 491 if (!taskChange && transferStartingWindow && changeSize == 2 492 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all 493 // changes are underneath another change. 494 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) 495 && allOccluded)) { 496 t.apply(); 497 finishT.apply(); 498 // Treat this as an abort since we are bypassing any merge logic and effectively 499 // finishing immediately. 500 onAbort(transitionToken); 501 releaseSurfaces(info); 502 return; 503 } 504 505 final ActiveTransition active = mActiveTransitions.get(activeIdx); 506 active.mInfo = info; 507 active.mStartT = t; 508 active.mFinishT = finishT; 509 setupStartState(active.mInfo, active.mStartT, active.mFinishT); 510 511 if (activeIdx > 0) { 512 // This is now playing at the same time as an existing animation, so try merging it. 513 attemptMergeTransition(mActiveTransitions.get(0), active); 514 return; 515 } 516 // The normal case, just play it. 517 playTransition(active); 518 } 519 520 /** 521 * Attempt to merge by delegating the transition start to the handler of the currently 522 * playing transition. 523 */ attemptMergeTransition(@onNull ActiveTransition playing, @NonNull ActiveTransition merging)524 void attemptMergeTransition(@NonNull ActiveTransition playing, 525 @NonNull ActiveTransition merging) { 526 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" 527 + " another transition %s is still animating. Notify the animating transition" 528 + " in case they can be merged", merging.mToken, playing.mToken); 529 playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT, 530 playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb)); 531 } 532 playTransition(@onNull ActiveTransition active)533 private void playTransition(@NonNull ActiveTransition active) { 534 for (int i = 0; i < mObservers.size(); ++i) { 535 mObservers.get(i).onTransitionStarting(active.mToken); 536 } 537 538 setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT); 539 540 // If a handler already chose to run this animation, try delegating to it first. 541 if (active.mHandler != null) { 542 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", 543 active.mHandler); 544 boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo, 545 active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb)); 546 if (consumed) { 547 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); 548 return; 549 } 550 } 551 // Otherwise give every other handler a chance 552 active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT, 553 active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler); 554 } 555 556 /** 557 * Gives every handler (in order) a chance to animate until one consumes the transition. 558 * @return the handler which consumed the transition. 559 */ dispatchTransition(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip)560 TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, 561 @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, 562 @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) { 563 for (int i = mHandlers.size() - 1; i >= 0; --i) { 564 if (mHandlers.get(i) == skip) continue; 565 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", 566 mHandlers.get(i)); 567 boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, 568 finishCB); 569 if (consumed) { 570 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", 571 mHandlers.get(i)); 572 return mHandlers.get(i); 573 } 574 } 575 throw new IllegalStateException( 576 "This shouldn't happen, maybe the default handler is broken."); 577 } 578 579 /** 580 * Gives every handler (in order) a chance to handle request until one consumes the transition. 581 * @return the WindowContainerTransaction given by the handler which consumed the transition. 582 */ dispatchRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip)583 public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition, 584 @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) { 585 for (int i = mHandlers.size() - 1; i >= 0; --i) { 586 if (mHandlers.get(i) == skip) continue; 587 WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request); 588 if (wct != null) { 589 return wct; 590 } 591 } 592 return null; 593 } 594 595 /** Special version of finish just for dealing with no-op/invalid transitions. */ onAbort(IBinder transition)596 private void onAbort(IBinder transition) { 597 onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */); 598 } 599 onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)600 private void onFinish(IBinder transition, 601 @Nullable WindowContainerTransaction wct, 602 @Nullable WindowContainerTransactionCallback wctCB) { 603 onFinish(transition, wct, wctCB, false /* abort */); 604 } 605 606 /** 607 * Releases an info's animation-surfaces. These don't need to persist and we need to release 608 * them asap so that SF can free memory sooner. 609 */ releaseSurfaces(@ullable TransitionInfo info)610 private void releaseSurfaces(@Nullable TransitionInfo info) { 611 if (info == null) return; 612 info.releaseAnimSurfaces(); 613 } 614 onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB, boolean abort)615 private void onFinish(IBinder transition, 616 @Nullable WindowContainerTransaction wct, 617 @Nullable WindowContainerTransactionCallback wctCB, 618 boolean abort) { 619 int activeIdx = findActiveTransition(transition); 620 if (activeIdx < 0) { 621 Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or " 622 + " a handler didn't properly deal with a merge.", new RuntimeException()); 623 return; 624 } else if (activeIdx > 0) { 625 // This transition was merged. 626 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:" 627 + " %s", abort, transition); 628 final ActiveTransition active = mActiveTransitions.get(activeIdx); 629 active.mMerged = true; 630 active.mAborted = abort; 631 if (active.mHandler != null) { 632 active.mHandler.onTransitionConsumed( 633 active.mToken, abort, abort ? null : active.mFinishT); 634 } 635 for (int i = 0; i < mObservers.size(); ++i) { 636 mObservers.get(i).onTransitionMerged( 637 active.mToken, mActiveTransitions.get(0).mToken); 638 } 639 return; 640 } 641 final ActiveTransition active = mActiveTransitions.get(activeIdx); 642 active.mAborted = abort; 643 if (active.mAborted && active.mHandler != null) { 644 // Notifies to clean-up the aborted transition. 645 active.mHandler.onTransitionConsumed( 646 transition, true /* aborted */, null /* finishTransaction */); 647 } 648 for (int i = 0; i < mObservers.size(); ++i) { 649 mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted); 650 } 651 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 652 "Transition animation finished (abort=%b), notifying core %s", abort, transition); 653 if (active.mStartT != null) { 654 // Applied by now, so close immediately. Do not set to null yet, though, since nullness 655 // is used later to disambiguate malformed transitions. 656 active.mStartT.close(); 657 } 658 // Merge all relevant transactions together 659 SurfaceControl.Transaction fullFinish = active.mFinishT; 660 for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) { 661 final ActiveTransition toMerge = mActiveTransitions.get(iA); 662 if (!toMerge.mMerged) break; 663 // aborted transitions have no start/finish transactions 664 if (mActiveTransitions.get(iA).mStartT == null) break; 665 if (fullFinish == null) { 666 fullFinish = new SurfaceControl.Transaction(); 667 } 668 // Include start. It will be a no-op if it was already applied. Otherwise, we need it 669 // to maintain consistent state. 670 fullFinish.merge(mActiveTransitions.get(iA).mStartT); 671 fullFinish.merge(mActiveTransitions.get(iA).mFinishT); 672 } 673 if (fullFinish != null) { 674 fullFinish.apply(); 675 } 676 // Now perform all the finishes. 677 releaseSurfaces(active.mInfo); 678 mActiveTransitions.remove(activeIdx); 679 mOrganizer.finishTransition(transition, wct, wctCB); 680 while (activeIdx < mActiveTransitions.size()) { 681 if (!mActiveTransitions.get(activeIdx).mMerged) break; 682 ActiveTransition merged = mActiveTransitions.remove(activeIdx); 683 mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); 684 releaseSurfaces(merged.mInfo); 685 } 686 // sift through aborted transitions 687 while (mActiveTransitions.size() > activeIdx 688 && mActiveTransitions.get(activeIdx).mAborted) { 689 ActiveTransition aborted = mActiveTransitions.remove(activeIdx); 690 // Notifies to clean-up the aborted transition. 691 if (aborted.mHandler != null) { 692 aborted.mHandler.onTransitionConsumed( 693 transition, true /* aborted */, null /* finishTransaction */); 694 } 695 mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */); 696 for (int i = 0; i < mObservers.size(); ++i) { 697 mObservers.get(i).onTransitionFinished(aborted.mToken, true); 698 } 699 releaseSurfaces(aborted.mInfo); 700 } 701 if (mActiveTransitions.size() <= activeIdx) { 702 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations " 703 + "finished"); 704 // Run all runnables from the run-when-idle queue. 705 for (int i = 0; i < mRunWhenIdleQueue.size(); i++) { 706 mRunWhenIdleQueue.get(i).run(); 707 } 708 mRunWhenIdleQueue.clear(); 709 return; 710 } 711 // Start animating the next active transition 712 final ActiveTransition next = mActiveTransitions.get(activeIdx); 713 if (next.mInfo == null) { 714 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one" 715 + " finished, but it isn't ready yet."); 716 return; 717 } 718 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one" 719 + " finished, so start the next one."); 720 playTransition(next); 721 // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have 722 // finished immediately) 723 activeIdx = findActiveTransition(next.mToken); 724 if (activeIdx < 0) { 725 // This means 'next' finished immediately and thus re-entered this function. Since 726 // that is the case, just return here since all relevant logic has already run in the 727 // re-entered call. 728 return; 729 } 730 731 // This logic is also convoluted because 'next' may finish immediately in response to any of 732 // the merge requests (eg. if it decided to "cancel" itself). 733 int mergeIdx = activeIdx + 1; 734 while (mergeIdx < mActiveTransitions.size()) { 735 ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx); 736 if (mergeCandidate.mAborted) { 737 // transition was aborted, so we can skip for now (still leave it in the list 738 // so that it gets cleaned-up in the right order). 739 ++mergeIdx; 740 continue; 741 } 742 if (mergeCandidate.mMerged) { 743 throw new IllegalStateException("Can't merge a transition after not-merging" 744 + " a preceding one."); 745 } 746 attemptMergeTransition(next, mergeCandidate); 747 mergeIdx = findActiveTransition(mergeCandidate.mToken); 748 if (mergeIdx < 0) { 749 // This means 'next' finished immediately and thus re-entered this function. Since 750 // that is the case, just return here since all relevant logic has already run in 751 // the re-entered call. 752 return; 753 } 754 ++mergeIdx; 755 } 756 } 757 requestStartTransition(@onNull IBinder transitionToken, @Nullable TransitionRequestInfo request)758 void requestStartTransition(@NonNull IBinder transitionToken, 759 @Nullable TransitionRequestInfo request) { 760 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", 761 transitionToken, request); 762 if (findActiveTransition(transitionToken) >= 0) { 763 throw new RuntimeException("Transition already started " + transitionToken); 764 } 765 final ActiveTransition active = new ActiveTransition(); 766 WindowContainerTransaction wct = null; 767 for (int i = mHandlers.size() - 1; i >= 0; --i) { 768 wct = mHandlers.get(i).handleRequest(transitionToken, request); 769 if (wct != null) { 770 active.mHandler = mHandlers.get(i); 771 break; 772 } 773 } 774 if (request.getDisplayChange() != null) { 775 TransitionRequestInfo.DisplayChange change = request.getDisplayChange(); 776 if (change.getEndRotation() != change.getStartRotation()) { 777 // Is a rotation, so dispatch to all displayChange listeners 778 if (wct == null) { 779 wct = new WindowContainerTransaction(); 780 } 781 mDisplayController.getChangeController().dispatchOnDisplayChange(wct, 782 change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), 783 null /* newDisplayAreaInfo */); 784 } 785 } 786 mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); 787 active.mToken = transitionToken; 788 mActiveTransitions.add(active); 789 } 790 791 /** Start a new transition directly. */ startTransition(@indowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler)792 public IBinder startTransition(@WindowManager.TransitionType int type, 793 @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { 794 final ActiveTransition active = new ActiveTransition(); 795 active.mHandler = handler; 796 active.mToken = mOrganizer.startNewTransition(type, wct); 797 mActiveTransitions.add(active); 798 return active.mToken; 799 } 800 801 /** 802 * Interface for a callback that must be called after a TransitionHandler finishes playing an 803 * animation. 804 */ 805 public interface TransitionFinishCallback { 806 /** 807 * This must be called on the main thread when a transition finishes playing an animation. 808 * The transition must not touch the surfaces after this has been called. 809 * 810 * @param wct A WindowContainerTransaction to run along with the transition clean-up. 811 * @param wctCB A sync callback that will be run when the transition clean-up is done and 812 * wct has been applied. 813 */ onTransitionFinished(@ullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)814 void onTransitionFinished(@Nullable WindowContainerTransaction wct, 815 @Nullable WindowContainerTransactionCallback wctCB); 816 } 817 818 /** 819 * Interface for something which can handle a subset of transitions. 820 */ 821 public interface TransitionHandler { 822 /** 823 * Starts a transition animation. This is always called if handleRequest returned non-null 824 * for a particular transition. Otherwise, it is only called if no other handler before 825 * it handled the transition. 826 * @param startTransaction the transaction given to the handler to be applied before the 827 * transition animation. Note the handler is expected to call on 828 * {@link SurfaceControl.Transaction#apply()} for startTransaction. 829 * @param finishTransaction the transaction given to the handler to be applied after the 830 * transition animation. Unlike startTransaction, the handler is NOT 831 * expected to apply this transaction. The Transition system will 832 * apply it when finishCallback is called. 833 * @param finishCallback Call this when finished. This MUST be called on main thread. 834 * @return true if transition was handled, false if not (falls-back to default). 835 */ startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)836 boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 837 @NonNull SurfaceControl.Transaction startTransaction, 838 @NonNull SurfaceControl.Transaction finishTransaction, 839 @NonNull TransitionFinishCallback finishCallback); 840 841 /** 842 * Attempts to merge a different transition's animation into an animation that this handler 843 * is currently playing. If a merge is not possible/supported, this should be a no-op. 844 * 845 * This gets called if another transition becomes ready while this handler is still playing 846 * an animation. This is called regardless of whether this handler claims to support that 847 * particular transition or not. 848 * 849 * When this happens, there are 2 options: 850 * 1. Do nothing. This effectively rejects the merge request. This is the "safest" option. 851 * 2. Merge the incoming transition into this one. The implementation is up to this 852 * handler. To indicate that this handler has "consumed" the merge transition, it 853 * must call the finishCallback immediately, or at-least before the original 854 * transition's finishCallback is called. 855 * 856 * @param transition This is the transition that wants to be merged. 857 * @param info Information about what is changing in the transition. 858 * @param t Contains surface changes that resulted from the transition. 859 * @param mergeTarget This is the transition that we are attempting to merge with (ie. the 860 * one this handler is currently already animating). 861 * @param finishCallback Call this if merged. This MUST be called on main thread. 862 */ mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback)863 default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 864 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 865 @NonNull TransitionFinishCallback finishCallback) { } 866 867 /** 868 * Potentially handles a startTransition request. 869 * 870 * @param transition The transition whose start is being requested. 871 * @param request Information about what is requested. 872 * @return WCT to apply with transition-start or null. If a WCT is returned here, this 873 * handler will be the first in line to animate. 874 */ 875 @Nullable handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)876 WindowContainerTransaction handleRequest(@NonNull IBinder transition, 877 @NonNull TransitionRequestInfo request); 878 879 /** 880 * Called when a transition which was already "claimed" by this handler has been merged 881 * into another animation or has been aborted. Gives this handler a chance to clean-up any 882 * expectations. 883 * 884 * @param transition The transition been consumed. 885 * @param aborted Whether the transition is aborted or not. 886 * @param finishTransaction The transaction to be applied after the transition animated. 887 */ onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction)888 default void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 889 @Nullable SurfaceControl.Transaction finishTransaction) { } 890 891 /** 892 * Sets transition animation scale settings value to handler. 893 * 894 * @param scale The setting value of transition animation scale. 895 */ setAnimScaleSetting(float scale)896 default void setAnimScaleSetting(float scale) {} 897 } 898 899 /** 900 * Interface for something that needs to know the lifecycle of some transitions, but never 901 * handles any transition by itself. 902 */ 903 public interface TransitionObserver { 904 /** 905 * Called when the transition is ready to play. It may later be merged into other 906 * transitions. Note this doesn't mean this transition will be played anytime soon. 907 * 908 * @param transition the unique token of this transition 909 * @param startTransaction the transaction given to the handler to be applied before the 910 * transition animation. This will be applied when the transition 911 * handler that handles this transition starts the transition. 912 * @param finishTransaction the transaction given to the handler to be applied after the 913 * transition animation. The Transition system will apply it when 914 * finishCallback is called by the transition handler. 915 */ onTransitionReady(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)916 void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, 917 @NonNull SurfaceControl.Transaction startTransaction, 918 @NonNull SurfaceControl.Transaction finishTransaction); 919 920 /** 921 * Called when the transition is starting to play. It isn't called for merged transitions. 922 * 923 * @param transition the unique token of this transition 924 */ onTransitionStarting(@onNull IBinder transition)925 void onTransitionStarting(@NonNull IBinder transition); 926 927 /** 928 * Called when a transition is merged into another transition. There won't be any following 929 * lifecycle calls for the merged transition. 930 * 931 * @param merged the unique token of the transition that's merged to another one 932 * @param playing the unique token of the transition that accepts the merge 933 */ onTransitionMerged(@onNull IBinder merged, @NonNull IBinder playing)934 void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing); 935 936 /** 937 * Called when the transition is finished. This isn't called for merged transitions. 938 * 939 * @param transition the unique token of this transition 940 * @param aborted {@code true} if this transition is aborted; {@code false} otherwise. 941 */ onTransitionFinished(@onNull IBinder transition, boolean aborted)942 void onTransitionFinished(@NonNull IBinder transition, boolean aborted); 943 } 944 945 @BinderThread 946 private class TransitionPlayerImpl extends ITransitionPlayer.Stub { 947 @Override onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)948 public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, 949 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) 950 throws RemoteException { 951 mMainExecutor.execute(() -> Transitions.this.onTransitionReady( 952 iBinder, transitionInfo, t, finishT)); 953 } 954 955 @Override requestStartTransition(IBinder iBinder, TransitionRequestInfo request)956 public void requestStartTransition(IBinder iBinder, 957 TransitionRequestInfo request) throws RemoteException { 958 mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request)); 959 } 960 } 961 962 /** 963 * The interface for calls from outside the Shell, within the host process. 964 */ 965 @ExternalThread 966 private class ShellTransitionImpl implements ShellTransitions { 967 @Override registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)968 public void registerRemote(@NonNull TransitionFilter filter, 969 @NonNull RemoteTransition remoteTransition) { 970 mMainExecutor.execute(() -> { 971 mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 972 }); 973 } 974 975 @Override unregisterRemote(@onNull RemoteTransition remoteTransition)976 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 977 mMainExecutor.execute(() -> { 978 mRemoteTransitionHandler.removeFiltered(remoteTransition); 979 }); 980 } 981 } 982 983 /** 984 * The interface for calls from outside the host process. 985 */ 986 @BinderThread 987 private static class IShellTransitionsImpl extends IShellTransitions.Stub 988 implements ExternalInterfaceBinder { 989 private Transitions mTransitions; 990 IShellTransitionsImpl(Transitions transitions)991 IShellTransitionsImpl(Transitions transitions) { 992 mTransitions = transitions; 993 } 994 995 /** 996 * Invalidates this instance, preventing future calls from updating the controller. 997 */ 998 @Override invalidate()999 public void invalidate() { 1000 mTransitions = null; 1001 } 1002 1003 @Override registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)1004 public void registerRemote(@NonNull TransitionFilter filter, 1005 @NonNull RemoteTransition remoteTransition) { 1006 executeRemoteCallWithTaskPermission(mTransitions, "registerRemote", 1007 (transitions) -> { 1008 transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 1009 }); 1010 } 1011 1012 @Override unregisterRemote(@onNull RemoteTransition remoteTransition)1013 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 1014 executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote", 1015 (transitions) -> { 1016 transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); 1017 }); 1018 } 1019 } 1020 1021 private class SettingsObserver extends ContentObserver { 1022 SettingsObserver()1023 SettingsObserver() { 1024 super(null); 1025 } 1026 1027 @Override onChange(boolean selfChange)1028 public void onChange(boolean selfChange) { 1029 super.onChange(selfChange); 1030 mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); 1031 1032 mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting)); 1033 } 1034 } 1035 } 1036