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