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.window.TransitionInfo.FLAG_IS_WALLPAPER; 27 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 28 29 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.content.ContentResolver; 34 import android.content.Context; 35 import android.database.ContentObserver; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.os.SystemProperties; 39 import android.provider.Settings; 40 import android.util.Log; 41 import android.view.SurfaceControl; 42 import android.view.WindowManager; 43 import android.window.ITransitionPlayer; 44 import android.window.RemoteTransition; 45 import android.window.TransitionFilter; 46 import android.window.TransitionInfo; 47 import android.window.TransitionMetrics; 48 import android.window.TransitionRequestInfo; 49 import android.window.WindowContainerTransaction; 50 import android.window.WindowContainerTransactionCallback; 51 import android.window.WindowOrganizer; 52 53 import androidx.annotation.BinderThread; 54 55 import com.android.internal.R; 56 import com.android.internal.annotations.VisibleForTesting; 57 import com.android.internal.protolog.common.ProtoLog; 58 import com.android.wm.shell.ShellTaskOrganizer; 59 import com.android.wm.shell.common.DisplayController; 60 import com.android.wm.shell.common.RemoteCallable; 61 import com.android.wm.shell.common.ShellExecutor; 62 import com.android.wm.shell.common.TransactionPool; 63 import com.android.wm.shell.common.annotations.ExternalThread; 64 import com.android.wm.shell.protolog.ShellProtoLogGroup; 65 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 69 /** Plays transition animations */ 70 public class Transitions implements RemoteCallable<Transitions> { 71 static final String TAG = "ShellTransitions"; 72 73 /** Set to {@code true} to enable shell transitions. */ 74 public static final boolean ENABLE_SHELL_TRANSITIONS = 75 SystemProperties.getBoolean("persist.debug.shell_transit", false); 76 77 /** Transition type for dismissing split-screen via dragging the divider off the screen. */ 78 public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1; 79 80 /** Transition type for launching 2 tasks simultaneously. */ 81 public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2; 82 83 /** Transition type for exiting PIP via the Shell, via pressing the expand button. */ 84 public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3; 85 86 /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */ 87 public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4; 88 89 /** Transition type for entering split by opening an app into side-stage. */ 90 public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5; 91 92 private final WindowOrganizer mOrganizer; 93 private final Context mContext; 94 private final ShellExecutor mMainExecutor; 95 private final ShellExecutor mAnimExecutor; 96 private final TransitionPlayerImpl mPlayerImpl; 97 private final RemoteTransitionHandler mRemoteTransitionHandler; 98 private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); 99 100 /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ 101 private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); 102 103 private float mTransitionAnimationScaleSetting = 1.0f; 104 105 private static final class ActiveTransition { 106 IBinder mToken; 107 TransitionHandler mHandler; 108 boolean mMerged; 109 boolean mAborted; 110 TransitionInfo mInfo; 111 SurfaceControl.Transaction mStartT; 112 SurfaceControl.Transaction mFinishT; 113 } 114 115 /** Keeps track of currently playing transitions in the order of receipt. */ 116 private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>(); 117 Transitions(@onNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor)118 public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, 119 @NonNull DisplayController displayController, @NonNull Context context, 120 @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { 121 mOrganizer = organizer; 122 mContext = context; 123 mMainExecutor = mainExecutor; 124 mAnimExecutor = animExecutor; 125 mPlayerImpl = new TransitionPlayerImpl(); 126 // The very last handler (0 in the list) should be the default one. 127 mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor, 128 animExecutor)); 129 // Next lowest priority is remote transitions. 130 mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor); 131 mHandlers.add(mRemoteTransitionHandler); 132 133 ContentResolver resolver = context.getContentResolver(); 134 mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver, 135 Settings.Global.TRANSITION_ANIMATION_SCALE, 136 context.getResources().getFloat( 137 R.dimen.config_appTransitionAnimationDurationScaleDefault)); 138 dispatchAnimScaleSetting(mTransitionAnimationScaleSetting); 139 140 resolver.registerContentObserver( 141 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false, 142 new SettingsObserver()); 143 } 144 Transitions()145 private Transitions() { 146 mOrganizer = null; 147 mContext = null; 148 mMainExecutor = null; 149 mAnimExecutor = null; 150 mPlayerImpl = null; 151 mRemoteTransitionHandler = null; 152 } 153 asRemoteTransitions()154 public ShellTransitions asRemoteTransitions() { 155 return mImpl; 156 } 157 158 @Override getContext()159 public Context getContext() { 160 return mContext; 161 } 162 163 @Override getRemoteCallExecutor()164 public ShellExecutor getRemoteCallExecutor() { 165 return mMainExecutor; 166 } 167 dispatchAnimScaleSetting(float scale)168 private void dispatchAnimScaleSetting(float scale) { 169 for (int i = mHandlers.size() - 1; i >= 0; --i) { 170 mHandlers.get(i).setAnimScaleSetting(scale); 171 } 172 } 173 174 /** Register this transition handler with Core */ register(ShellTaskOrganizer taskOrganizer)175 public void register(ShellTaskOrganizer taskOrganizer) { 176 if (mPlayerImpl == null) return; 177 taskOrganizer.registerTransitionPlayer(mPlayerImpl); 178 // Pre-load the instance. 179 TransitionMetrics.getInstance(); 180 } 181 182 /** 183 * Adds a handler candidate. 184 * @see TransitionHandler 185 */ addHandler(@onNull TransitionHandler handler)186 public void addHandler(@NonNull TransitionHandler handler) { 187 mHandlers.add(handler); 188 } 189 getMainExecutor()190 public ShellExecutor getMainExecutor() { 191 return mMainExecutor; 192 } 193 getAnimExecutor()194 public ShellExecutor getAnimExecutor() { 195 return mAnimExecutor; 196 } 197 198 /** Only use this in tests. This is used to avoid running animations during tests. */ 199 @VisibleForTesting replaceDefaultHandlerForTest(TransitionHandler handler)200 void replaceDefaultHandlerForTest(TransitionHandler handler) { 201 mHandlers.set(0, handler); 202 } 203 204 /** Register a remote transition to be used when `filter` matches an incoming transition */ registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)205 public void registerRemote(@NonNull TransitionFilter filter, 206 @NonNull RemoteTransition remoteTransition) { 207 mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 208 } 209 210 /** Unregisters a remote transition and all associated filters */ unregisterRemote(@onNull RemoteTransition remoteTransition)211 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 212 mRemoteTransitionHandler.removeFiltered(remoteTransition); 213 } 214 215 /** @return true if the transition was triggered by opening something vs closing something */ isOpeningType(@indowManager.TransitionType int type)216 public static boolean isOpeningType(@WindowManager.TransitionType int type) { 217 return type == TRANSIT_OPEN 218 || type == TRANSIT_TO_FRONT 219 || type == TRANSIT_KEYGUARD_GOING_AWAY; 220 } 221 222 /** @return true if the transition was triggered by closing something vs opening something */ isClosingType(@indowManager.TransitionType int type)223 public static boolean isClosingType(@WindowManager.TransitionType int type) { 224 return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; 225 } 226 227 /** 228 * Sets up visibility/alpha/transforms to resemble the starting state of an animation. 229 */ setupStartState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)230 private static void setupStartState(@NonNull TransitionInfo info, 231 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 232 boolean isOpening = isOpeningType(info.getType()); 233 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 234 final TransitionInfo.Change change = info.getChanges().get(i); 235 final SurfaceControl leash = change.getLeash(); 236 final int mode = info.getChanges().get(i).getMode(); 237 238 // Don't move anything that isn't independent within its parents 239 if (!TransitionInfo.isIndependent(change, info)) { 240 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { 241 t.show(leash); 242 t.setMatrix(leash, 1, 0, 0, 1); 243 t.setAlpha(leash, 1.f); 244 t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); 245 } 246 continue; 247 } 248 249 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { 250 t.show(leash); 251 t.setMatrix(leash, 1, 0, 0, 1); 252 if (isOpening 253 // If this is a transferred starting window, we want it immediately visible. 254 && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { 255 t.setAlpha(leash, 0.f); 256 // fix alpha in finish transaction in case the animator itself no-ops. 257 finishT.setAlpha(leash, 1.f); 258 } 259 } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { 260 // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates. 261 // As a result, we actually can't hide it's WindowToken because there may not be a 262 // transition associated with it becoming visible again. Fortunately, since it is 263 // always z-ordered to the back, we don't have to worry about it flickering to the 264 // front during reparenting, so the hide here isn't necessary for it. 265 if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) { 266 finishT.hide(leash); 267 } 268 } 269 } 270 } 271 272 /** 273 * Reparents all participants into a shared parent and orders them based on: the global transit 274 * type, their transit mode, and their destination z-order. 275 */ setupAnimHierarchy(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)276 private static void setupAnimHierarchy(@NonNull TransitionInfo info, 277 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 278 boolean isOpening = isOpeningType(info.getType()); 279 if (info.getRootLeash().isValid()) { 280 t.show(info.getRootLeash()); 281 } 282 // Put animating stuff above this line and put static stuff below it. 283 int zSplitLine = info.getChanges().size(); 284 // changes should be ordered top-to-bottom in z 285 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 286 final TransitionInfo.Change change = info.getChanges().get(i); 287 final SurfaceControl leash = change.getLeash(); 288 final int mode = info.getChanges().get(i).getMode(); 289 290 // Don't reparent anything that isn't independent within its parents 291 if (!TransitionInfo.isIndependent(change, info)) { 292 continue; 293 } 294 295 boolean hasParent = change.getParent() != null; 296 297 if (!hasParent) { 298 t.reparent(leash, info.getRootLeash()); 299 t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, 300 change.getStartAbsBounds().top - info.getRootOffset().y); 301 } 302 // Put all the OPEN/SHOW on top 303 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { 304 if (isOpening) { 305 // put on top 306 t.setLayer(leash, zSplitLine + info.getChanges().size() - i); 307 } else { 308 // put on bottom 309 t.setLayer(leash, zSplitLine - i); 310 } 311 } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { 312 if (isOpening) { 313 // put on bottom and leave visible 314 t.setLayer(leash, zSplitLine - i); 315 } else { 316 // put on top 317 t.setLayer(leash, zSplitLine + info.getChanges().size() - i); 318 } 319 } else { // CHANGE or other 320 t.setLayer(leash, zSplitLine + info.getChanges().size() - i); 321 } 322 } 323 } 324 findActiveTransition(IBinder token)325 private int findActiveTransition(IBinder token) { 326 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 327 if (mActiveTransitions.get(i).mToken == token) return i; 328 } 329 return -1; 330 } 331 332 @VisibleForTesting onTransitionReady(@onNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)333 void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, 334 @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 335 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", 336 transitionToken, info); 337 final int activeIdx = findActiveTransition(transitionToken); 338 if (activeIdx < 0) { 339 throw new IllegalStateException("Got transitionReady for non-active transition " 340 + transitionToken + ". expecting one of " 341 + Arrays.toString(mActiveTransitions.stream().map( 342 activeTransition -> activeTransition.mToken).toArray())); 343 } 344 if (!info.getRootLeash().isValid()) { 345 // Invalid root-leash implies that the transition is empty/no-op, so just do 346 // housekeeping and return. 347 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s", 348 transitionToken, info); 349 t.apply(); 350 onAbort(transitionToken); 351 return; 352 } 353 354 final ActiveTransition active = mActiveTransitions.get(activeIdx); 355 active.mInfo = info; 356 active.mStartT = t; 357 active.mFinishT = finishT; 358 setupStartState(active.mInfo, active.mStartT, active.mFinishT); 359 360 if (activeIdx > 0) { 361 // This is now playing at the same time as an existing animation, so try merging it. 362 attemptMergeTransition(mActiveTransitions.get(0), active); 363 return; 364 } 365 // The normal case, just play it. 366 playTransition(active); 367 } 368 369 /** 370 * Attempt to merge by delegating the transition start to the handler of the currently 371 * playing transition. 372 */ attemptMergeTransition(@onNull ActiveTransition playing, @NonNull ActiveTransition merging)373 void attemptMergeTransition(@NonNull ActiveTransition playing, 374 @NonNull ActiveTransition merging) { 375 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" 376 + " another transition %s is still animating. Notify the animating transition" 377 + " in case they can be merged", merging.mToken, playing.mToken); 378 playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT, 379 playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb)); 380 } 381 startAnimation(@onNull ActiveTransition active, TransitionHandler handler)382 boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) { 383 return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT, 384 (wct, cb) -> onFinish(active.mToken, wct, cb)); 385 } 386 playTransition(@onNull ActiveTransition active)387 void playTransition(@NonNull ActiveTransition active) { 388 setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT); 389 390 // If a handler already chose to run this animation, try delegating to it first. 391 if (active.mHandler != null) { 392 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", 393 active.mHandler); 394 if (startAnimation(active, active.mHandler)) { 395 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); 396 return; 397 } 398 } 399 // Otherwise give every other handler a chance (in order) 400 for (int i = mHandlers.size() - 1; i >= 0; --i) { 401 if (mHandlers.get(i) == active.mHandler) continue; 402 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", 403 mHandlers.get(i)); 404 if (startAnimation(active, mHandlers.get(i))) { 405 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", 406 mHandlers.get(i)); 407 active.mHandler = mHandlers.get(i); 408 return; 409 } 410 } 411 throw new IllegalStateException( 412 "This shouldn't happen, maybe the default handler is broken."); 413 } 414 415 /** Special version of finish just for dealing with no-op/invalid transitions. */ onAbort(IBinder transition)416 private void onAbort(IBinder transition) { 417 onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */); 418 } 419 onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)420 private void onFinish(IBinder transition, 421 @Nullable WindowContainerTransaction wct, 422 @Nullable WindowContainerTransactionCallback wctCB) { 423 onFinish(transition, wct, wctCB, false /* abort */); 424 } 425 onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB, boolean abort)426 private void onFinish(IBinder transition, 427 @Nullable WindowContainerTransaction wct, 428 @Nullable WindowContainerTransactionCallback wctCB, 429 boolean abort) { 430 int activeIdx = findActiveTransition(transition); 431 if (activeIdx < 0) { 432 Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or " 433 + " a handler didn't properly deal with a merge.", new RuntimeException()); 434 return; 435 } else if (activeIdx > 0) { 436 // This transition was merged. 437 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:" 438 + " %s", abort, transition); 439 final ActiveTransition active = mActiveTransitions.get(activeIdx); 440 active.mMerged = true; 441 active.mAborted = abort; 442 if (active.mHandler != null) { 443 active.mHandler.onTransitionMerged(active.mToken); 444 } 445 return; 446 } 447 mActiveTransitions.get(activeIdx).mAborted = abort; 448 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 449 "Transition animation finished (abort=%b), notifying core %s", abort, transition); 450 // Merge all relevant transactions together 451 SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT; 452 for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) { 453 final ActiveTransition toMerge = mActiveTransitions.get(iA); 454 if (!toMerge.mMerged) break; 455 // aborted transitions have no start/finish transactions 456 if (mActiveTransitions.get(iA).mStartT == null) break; 457 if (fullFinish == null) { 458 fullFinish = new SurfaceControl.Transaction(); 459 } 460 // Include start. It will be a no-op if it was already applied. Otherwise, we need it 461 // to maintain consistent state. 462 fullFinish.merge(mActiveTransitions.get(iA).mStartT); 463 fullFinish.merge(mActiveTransitions.get(iA).mFinishT); 464 } 465 if (fullFinish != null) { 466 fullFinish.apply(); 467 } 468 // Now perform all the finishes. 469 mActiveTransitions.remove(activeIdx); 470 mOrganizer.finishTransition(transition, wct, wctCB); 471 while (activeIdx < mActiveTransitions.size()) { 472 if (!mActiveTransitions.get(activeIdx).mMerged) break; 473 ActiveTransition merged = mActiveTransitions.remove(activeIdx); 474 mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); 475 } 476 // sift through aborted transitions 477 while (mActiveTransitions.size() > activeIdx 478 && mActiveTransitions.get(activeIdx).mAborted) { 479 ActiveTransition aborted = mActiveTransitions.remove(activeIdx); 480 mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */); 481 } 482 if (mActiveTransitions.size() <= activeIdx) { 483 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations " 484 + "finished"); 485 return; 486 } 487 // Start animating the next active transition 488 final ActiveTransition next = mActiveTransitions.get(activeIdx); 489 if (next.mInfo == null) { 490 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one" 491 + " finished, but it isn't ready yet."); 492 return; 493 } 494 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one" 495 + " finished, so start the next one."); 496 playTransition(next); 497 // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have 498 // finished immediately) 499 activeIdx = findActiveTransition(next.mToken); 500 if (activeIdx < 0) { 501 // This means 'next' finished immediately and thus re-entered this function. Since 502 // that is the case, just return here since all relevant logic has already run in the 503 // re-entered call. 504 return; 505 } 506 507 // This logic is also convoluted because 'next' may finish immediately in response to any of 508 // the merge requests (eg. if it decided to "cancel" itself). 509 int mergeIdx = activeIdx + 1; 510 while (mergeIdx < mActiveTransitions.size()) { 511 ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx); 512 if (mergeCandidate.mAborted) { 513 // transition was aborted, so we can skip for now (still leave it in the list 514 // so that it gets cleaned-up in the right order). 515 ++mergeIdx; 516 continue; 517 } 518 if (mergeCandidate.mMerged) { 519 throw new IllegalStateException("Can't merge a transition after not-merging" 520 + " a preceding one."); 521 } 522 attemptMergeTransition(next, mergeCandidate); 523 mergeIdx = findActiveTransition(mergeCandidate.mToken); 524 if (mergeIdx < 0) { 525 // This means 'next' finished immediately and thus re-entered this function. Since 526 // that is the case, just return here since all relevant logic has already run in 527 // the re-entered call. 528 return; 529 } 530 ++mergeIdx; 531 } 532 } 533 requestStartTransition(@onNull IBinder transitionToken, @Nullable TransitionRequestInfo request)534 void requestStartTransition(@NonNull IBinder transitionToken, 535 @Nullable TransitionRequestInfo request) { 536 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", 537 transitionToken, request); 538 if (findActiveTransition(transitionToken) >= 0) { 539 throw new RuntimeException("Transition already started " + transitionToken); 540 } 541 final ActiveTransition active = new ActiveTransition(); 542 WindowContainerTransaction wct = null; 543 for (int i = mHandlers.size() - 1; i >= 0; --i) { 544 wct = mHandlers.get(i).handleRequest(transitionToken, request); 545 if (wct != null) { 546 active.mHandler = mHandlers.get(i); 547 break; 548 } 549 } 550 active.mToken = mOrganizer.startTransition( 551 request.getType(), transitionToken, wct); 552 mActiveTransitions.add(active); 553 } 554 555 /** Start a new transition directly. */ startTransition(@indowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler)556 public IBinder startTransition(@WindowManager.TransitionType int type, 557 @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { 558 final ActiveTransition active = new ActiveTransition(); 559 active.mHandler = handler; 560 active.mToken = mOrganizer.startTransition(type, null /* token */, wct); 561 mActiveTransitions.add(active); 562 return active.mToken; 563 } 564 565 /** 566 * Interface for a callback that must be called after a TransitionHandler finishes playing an 567 * animation. 568 */ 569 public interface TransitionFinishCallback { 570 /** 571 * This must be called on the main thread when a transition finishes playing an animation. 572 * The transition must not touch the surfaces after this has been called. 573 * 574 * @param wct A WindowContainerTransaction to run along with the transition clean-up. 575 * @param wctCB A sync callback that will be run when the transition clean-up is done and 576 * wct has been applied. 577 */ onTransitionFinished(@ullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)578 void onTransitionFinished(@Nullable WindowContainerTransaction wct, 579 @Nullable WindowContainerTransactionCallback wctCB); 580 } 581 582 /** 583 * Interface for something which can handle a subset of transitions. 584 */ 585 public interface TransitionHandler { 586 /** 587 * Starts a transition animation. This is always called if handleRequest returned non-null 588 * for a particular transition. Otherwise, it is only called if no other handler before 589 * it handled the transition. 590 * @param startTransaction the transaction given to the handler to be applied before the 591 * transition animation. Note the handler is expected to call on 592 * {@link SurfaceControl.Transaction#apply()} for startTransaction. 593 * @param finishTransaction the transaction given to the handler to be applied after the 594 * transition animation. Unlike startTransaction, the handler is NOT 595 * expected to apply this transaction. The Transition system will 596 * apply it when finishCallback is called. 597 * @param finishCallback Call this when finished. This MUST be called on main thread. 598 * @return true if transition was handled, false if not (falls-back to default). 599 */ startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)600 boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 601 @NonNull SurfaceControl.Transaction startTransaction, 602 @NonNull SurfaceControl.Transaction finishTransaction, 603 @NonNull TransitionFinishCallback finishCallback); 604 605 /** 606 * Attempts to merge a different transition's animation into an animation that this handler 607 * is currently playing. If a merge is not possible/supported, this should be a no-op. 608 * 609 * This gets called if another transition becomes ready while this handler is still playing 610 * an animation. This is called regardless of whether this handler claims to support that 611 * particular transition or not. 612 * 613 * When this happens, there are 2 options: 614 * 1. Do nothing. This effectively rejects the merge request. This is the "safest" option. 615 * 2. Merge the incoming transition into this one. The implementation is up to this 616 * handler. To indicate that this handler has "consumed" the merge transition, it 617 * must call the finishCallback immediately, or at-least before the original 618 * transition's finishCallback is called. 619 * 620 * @param transition This is the transition that wants to be merged. 621 * @param info Information about what is changing in the transition. 622 * @param t Contains surface changes that resulted from the transition. 623 * @param mergeTarget This is the transition that we are attempting to merge with (ie. the 624 * one this handler is currently already animating). 625 * @param finishCallback Call this if merged. This MUST be called on main thread. 626 */ mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback)627 default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 628 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 629 @NonNull TransitionFinishCallback finishCallback) { } 630 631 /** 632 * Potentially handles a startTransition request. 633 * 634 * @param transition The transition whose start is being requested. 635 * @param request Information about what is requested. 636 * @return WCT to apply with transition-start or null. If a WCT is returned here, this 637 * handler will be the first in line to animate. 638 */ 639 @Nullable handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)640 WindowContainerTransaction handleRequest(@NonNull IBinder transition, 641 @NonNull TransitionRequestInfo request); 642 643 /** 644 * Called when a transition which was already "claimed" by this handler has been merged 645 * into another animation. Gives this handler a chance to clean-up any expectations. 646 */ onTransitionMerged(@onNull IBinder transition)647 default void onTransitionMerged(@NonNull IBinder transition) { } 648 649 /** 650 * Sets transition animation scale settings value to handler. 651 * 652 * @param scale The setting value of transition animation scale. 653 */ setAnimScaleSetting(float scale)654 default void setAnimScaleSetting(float scale) {} 655 } 656 657 @BinderThread 658 private class TransitionPlayerImpl extends ITransitionPlayer.Stub { 659 @Override onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)660 public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, 661 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) 662 throws RemoteException { 663 mMainExecutor.execute(() -> Transitions.this.onTransitionReady( 664 iBinder, transitionInfo, t, finishT)); 665 } 666 667 @Override requestStartTransition(IBinder iBinder, TransitionRequestInfo request)668 public void requestStartTransition(IBinder iBinder, 669 TransitionRequestInfo request) throws RemoteException { 670 mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request)); 671 } 672 } 673 674 /** 675 * The interface for calls from outside the Shell, within the host process. 676 */ 677 @ExternalThread 678 private class ShellTransitionImpl implements ShellTransitions { 679 private IShellTransitionsImpl mIShellTransitions; 680 681 @Override createExternalInterface()682 public IShellTransitions createExternalInterface() { 683 if (mIShellTransitions != null) { 684 mIShellTransitions.invalidate(); 685 } 686 mIShellTransitions = new IShellTransitionsImpl(Transitions.this); 687 return mIShellTransitions; 688 } 689 690 @Override registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)691 public void registerRemote(@NonNull TransitionFilter filter, 692 @NonNull RemoteTransition remoteTransition) { 693 mMainExecutor.execute(() -> { 694 mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 695 }); 696 } 697 698 @Override unregisterRemote(@onNull RemoteTransition remoteTransition)699 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 700 mMainExecutor.execute(() -> { 701 mRemoteTransitionHandler.removeFiltered(remoteTransition); 702 }); 703 } 704 } 705 706 /** 707 * The interface for calls from outside the host process. 708 */ 709 @BinderThread 710 private static class IShellTransitionsImpl extends IShellTransitions.Stub { 711 private Transitions mTransitions; 712 IShellTransitionsImpl(Transitions transitions)713 IShellTransitionsImpl(Transitions transitions) { 714 mTransitions = transitions; 715 } 716 717 /** 718 * Invalidates this instance, preventing future calls from updating the controller. 719 */ invalidate()720 void invalidate() { 721 mTransitions = null; 722 } 723 724 @Override registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)725 public void registerRemote(@NonNull TransitionFilter filter, 726 @NonNull RemoteTransition remoteTransition) { 727 executeRemoteCallWithTaskPermission(mTransitions, "registerRemote", 728 (transitions) -> { 729 transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition); 730 }); 731 } 732 733 @Override unregisterRemote(@onNull RemoteTransition remoteTransition)734 public void unregisterRemote(@NonNull RemoteTransition remoteTransition) { 735 executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote", 736 (transitions) -> { 737 transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition); 738 }); 739 } 740 } 741 742 private class SettingsObserver extends ContentObserver { 743 SettingsObserver()744 SettingsObserver() { 745 super(null); 746 } 747 748 @Override onChange(boolean selfChange)749 public void onChange(boolean selfChange) { 750 super.onChange(selfChange); 751 mTransitionAnimationScaleSetting = Settings.Global.getFloat( 752 mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, 753 mTransitionAnimationScaleSetting); 754 755 mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting)); 756 } 757 } 758 } 759