1 /* 2 * Copyright (C) 2023 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.recents; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 23 import static android.view.Display.DEFAULT_DISPLAY; 24 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; 25 import static android.view.WindowManager.TRANSIT_CHANGE; 26 import static android.view.WindowManager.TRANSIT_CLOSE; 27 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 28 import static android.view.WindowManager.TRANSIT_OPEN; 29 import static android.view.WindowManager.TRANSIT_PIP; 30 import static android.view.WindowManager.TRANSIT_SLEEP; 31 import static android.view.WindowManager.TRANSIT_TO_FRONT; 32 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX; 33 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; 34 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 35 36 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; 37 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; 38 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; 39 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; 40 import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; 41 import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION; 42 import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION; 43 44 import android.annotation.Nullable; 45 import android.annotation.SuppressLint; 46 import android.app.ActivityManager; 47 import android.app.ActivityTaskManager; 48 import android.app.IApplicationThread; 49 import android.app.PendingIntent; 50 import android.app.WindowConfiguration; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.graphics.Color; 54 import android.graphics.Rect; 55 import android.os.Binder; 56 import android.os.Bundle; 57 import android.os.IBinder; 58 import android.os.RemoteException; 59 import android.util.ArrayMap; 60 import android.util.IntArray; 61 import android.util.Pair; 62 import android.util.Slog; 63 import android.view.RemoteAnimationTarget; 64 import android.view.SurfaceControl; 65 import android.window.PictureInPictureSurfaceTransaction; 66 import android.window.TaskSnapshot; 67 import android.window.TransitionInfo; 68 import android.window.TransitionRequestInfo; 69 import android.window.WindowAnimationState; 70 import android.window.WindowContainerToken; 71 import android.window.WindowContainerTransaction; 72 73 import androidx.annotation.NonNull; 74 75 import com.android.internal.annotations.VisibleForTesting; 76 import com.android.internal.os.IResultReceiver; 77 import com.android.internal.protolog.ProtoLog; 78 import com.android.wm.shell.Flags; 79 import com.android.wm.shell.ShellTaskOrganizer; 80 import com.android.wm.shell.common.ShellExecutor; 81 import com.android.wm.shell.common.pip.PipUtils; 82 import com.android.wm.shell.protolog.ShellProtoLogGroup; 83 import com.android.wm.shell.shared.R; 84 import com.android.wm.shell.shared.TransitionUtil; 85 import com.android.wm.shell.sysui.ShellInit; 86 import com.android.wm.shell.transition.HomeTransitionObserver; 87 import com.android.wm.shell.transition.Transitions; 88 89 import java.util.ArrayList; 90 import java.util.function.Consumer; 91 92 /** 93 * Handles the Recents (overview) animation. Only one of these can run at a time. A recents 94 * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored. 95 */ 96 public class RecentsTransitionHandler implements Transitions.TransitionHandler, 97 Transitions.TransitionObserver { 98 private static final String TAG = "RecentsTransitionHandler"; 99 100 // A placeholder for a synthetic transition that isn't backed by a true system transition 101 public static final IBinder SYNTHETIC_TRANSITION = new Binder(); 102 103 private final Transitions mTransitions; 104 private final ShellTaskOrganizer mShellTaskOrganizer; 105 private final ShellExecutor mExecutor; 106 @Nullable 107 private final RecentTasksController mRecentTasksController; 108 private IApplicationThread mAnimApp = null; 109 private final ArrayList<RecentsController> mControllers = new ArrayList<>(); 110 private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>(); 111 112 /** 113 * List of other handlers which might need to mix recents with other things. These are checked 114 * in the order they are added. Ideally there should only be one. 115 */ 116 private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); 117 118 private final HomeTransitionObserver mHomeTransitionObserver; 119 private @Nullable Color mBackgroundColor; 120 RecentsTransitionHandler( @onNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, @Nullable RecentTasksController recentTasksController, @NonNull HomeTransitionObserver homeTransitionObserver)121 public RecentsTransitionHandler( 122 @NonNull ShellInit shellInit, 123 @NonNull ShellTaskOrganizer shellTaskOrganizer, 124 @NonNull Transitions transitions, 125 @Nullable RecentTasksController recentTasksController, 126 @NonNull HomeTransitionObserver homeTransitionObserver) { 127 mShellTaskOrganizer = shellTaskOrganizer; 128 mTransitions = transitions; 129 mExecutor = transitions.getMainExecutor(); 130 mRecentTasksController = recentTasksController; 131 mHomeTransitionObserver = homeTransitionObserver; 132 if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; 133 if (recentTasksController == null) return; 134 shellInit.addInitCallback(this::onInit, this); 135 } 136 onInit()137 private void onInit() { 138 mRecentTasksController.setTransitionHandler(this); 139 mTransitions.addHandler(this); 140 mTransitions.registerObserver(this); 141 } 142 143 /** Register a mixer handler. {@see RecentsMixedHandler}*/ addMixer(RecentsMixedHandler mixer)144 public void addMixer(RecentsMixedHandler mixer) { 145 mMixers.add(mixer); 146 } 147 148 /** Unregister a Mixed Handler */ removeMixer(RecentsMixedHandler mixer)149 public void removeMixer(RecentsMixedHandler mixer) { 150 mMixers.remove(mixer); 151 } 152 153 /** Adds the callback for receiving the state change of transition. */ addTransitionStateListener(RecentsTransitionStateListener listener)154 public void addTransitionStateListener(RecentsTransitionStateListener listener) { 155 mStateListeners.add(listener); 156 } 157 158 /** 159 * Sets a background color on the transition root layered behind the outgoing task. {@code null} 160 * may be used to clear any previously set colors to avoid showing a background at all. The 161 * color is always shown at full opacity. 162 */ setTransitionBackgroundColor(@ullable Color color)163 public void setTransitionBackgroundColor(@Nullable Color color) { 164 mBackgroundColor = color; 165 } 166 167 /** 168 * Starts a new real/synthetic recents transition. 169 */ 170 @VisibleForTesting startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener)171 public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, 172 IApplicationThread appThread, IRecentsAnimationRunner listener) { 173 // only care about latest one. 174 mAnimApp = appThread; 175 176 for (int i = 0; i < mStateListeners.size(); i++) { 177 mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_REQUESTED); 178 } 179 // TODO(b/366021931): Formalize this later 180 final boolean isSyntheticRequest = options.getBoolean( 181 "is_synthetic_recents_transition", /* defaultValue= */ false); 182 final IBinder transition; 183 if (isSyntheticRequest) { 184 transition = startSyntheticRecentsTransition(listener); 185 } else { 186 transition = startRealRecentsTransition(intent, fillIn, options, listener); 187 } 188 return transition; 189 } 190 191 /** 192 * Starts a synthetic recents transition that is not backed by a real WM transition. 193 */ startSyntheticRecentsTransition(@onNull IRecentsAnimationRunner listener)194 private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) { 195 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 196 "RecentsTransitionHandler.startRecentsTransition(synthetic)"); 197 final RecentsController lastController = getLastController(); 198 if (lastController != null) { 199 lastController.cancel(lastController.isSyntheticTransition() 200 ? "existing_running_synthetic_transition" 201 : "existing_running_transition"); 202 return null; 203 } 204 205 // Create a new synthetic transition and start it immediately 206 final RecentsController controller = new RecentsController(listener); 207 controller.startSyntheticTransition(); 208 mControllers.add(controller); 209 return SYNTHETIC_TRANSITION; 210 } 211 212 /** 213 * Starts a real WM-backed recents transition. 214 */ startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IRecentsAnimationRunner listener)215 private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, 216 IRecentsAnimationRunner listener) { 217 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 218 "RecentsTransitionHandler.startRecentsTransition"); 219 220 final WindowContainerTransaction wct = new WindowContainerTransaction(); 221 wct.sendPendingIntent(intent, fillIn, options); 222 223 // Find the mixed handler which should handle this request (if we are in a state where a 224 // mixed handler is needed). This is slightly convoluted because starting the transition 225 // requires the handler, but the mixed handler also needs a reference to the transition. 226 RecentsMixedHandler mixer = null; 227 Consumer<IBinder> setTransitionForMixer = null; 228 for (int i = 0; i < mMixers.size(); ++i) { 229 setTransitionForMixer = mMixers.get(i).handleRecentsRequest(); 230 if (setTransitionForMixer != null) { 231 mixer = mMixers.get(i); 232 break; 233 } 234 } 235 final int transitionType = Flags.enableRecentsBookendTransition() 236 ? TRANSIT_START_RECENTS_TRANSITION 237 : TRANSIT_TO_FRONT; 238 final IBinder transition = mTransitions.startTransition(transitionType, 239 wct, mixer == null ? this : mixer); 240 if (mixer != null) { 241 setTransitionForMixer.accept(transition); 242 } 243 244 final RecentsController controller = new RecentsController(listener); 245 if (transition != null) { 246 controller.setTransition(transition); 247 mControllers.add(controller); 248 } else { 249 controller.cancel("startRecentsTransition"); 250 } 251 return transition; 252 } 253 254 @Override handleRequest(IBinder transition, TransitionRequestInfo request)255 public WindowContainerTransaction handleRequest(IBinder transition, 256 TransitionRequestInfo request) { 257 if (mControllers.isEmpty()) { 258 // Ignore if there is no running recents transition 259 return null; 260 } 261 final RecentsController controller = mControllers.get(mControllers.size() - 1); 262 controller.handleMidTransitionRequest(request); 263 return null; 264 } 265 266 /** 267 * Returns if there is currently a pending or active recents transition. 268 */ 269 @Nullable getLastController()270 private RecentsController getLastController() { 271 return !mControllers.isEmpty() ? mControllers.getLast() : null; 272 } 273 274 /** 275 * Finds an existing controller for the provided {@param transition}, or {@code null} if none 276 * exists. 277 */ 278 @Nullable 279 @VisibleForTesting findController(@onNull IBinder transition)280 RecentsController findController(@NonNull IBinder transition) { 281 for (int i = mControllers.size() - 1; i >= 0; --i) { 282 final RecentsController controller = mControllers.get(i); 283 if (controller.mTransition == transition) { 284 return controller; 285 } 286 } 287 return null; 288 } 289 290 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback)291 public boolean startAnimation(IBinder transition, TransitionInfo info, 292 SurfaceControl.Transaction startTransaction, 293 SurfaceControl.Transaction finishTransaction, 294 Transitions.TransitionFinishCallback finishCallback) { 295 final RecentsController controller = findController(transition); 296 if (controller == null) { 297 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 298 "RecentsTransitionHandler.startAnimation: no controller found"); 299 return false; 300 } 301 final IApplicationThread animApp = mAnimApp; 302 mAnimApp = null; 303 if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { 304 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 305 "RecentsTransitionHandler.startAnimation: failed to start animation"); 306 return false; 307 } 308 Transitions.setRunningRemoteTransitionDelegate(animApp); 309 return true; 310 } 311 312 @Override mergeAnimation(IBinder transition, TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)313 public void mergeAnimation(IBinder transition, TransitionInfo info, 314 @NonNull SurfaceControl.Transaction startT, 315 @NonNull SurfaceControl.Transaction finishT, 316 IBinder mergeTarget, 317 Transitions.TransitionFinishCallback finishCallback) { 318 final RecentsController controller = findController(mergeTarget); 319 if (controller == null) { 320 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 321 "RecentsTransitionHandler.mergeAnimation: no controller found"); 322 return; 323 } 324 controller.merge(info, startT, finishT, finishCallback); 325 } 326 327 @Override onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction)328 public void onTransitionConsumed(IBinder transition, boolean aborted, 329 SurfaceControl.Transaction finishTransaction) { 330 // Only one recents transition can be handled at a time, but currently the first transition 331 // will trigger a no-op in the second transition which holds the active recents animation 332 // runner on the launcher side. For now, cancel all existing animations to ensure we 333 // don't get into a broken state with an orphaned animation runner, and later we can try to 334 // merge the latest transition into the currently running one 335 for (int i = mControllers.size() - 1; i >= 0; i--) { 336 mControllers.get(i).cancel("onTransitionConsumed"); 337 } 338 } 339 340 @Override onTransitionReady(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)341 public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, 342 @NonNull SurfaceControl.Transaction startTransaction, 343 @NonNull SurfaceControl.Transaction finishTransaction) { 344 RecentsController controller = findController(SYNTHETIC_TRANSITION); 345 if (controller != null) { 346 // Cancel the existing synthetic transition if there is one 347 controller.cancel("incoming_transition"); 348 } 349 } 350 351 /** There is only one of these and it gets reset on finish. */ 352 @VisibleForTesting 353 class RecentsController extends IRecentsAnimationController.Stub { 354 355 private final int mInstanceId; 356 357 private IRecentsAnimationRunner mListener; 358 private IBinder.DeathRecipient mDeathHandler; 359 private Transitions.TransitionFinishCallback mFinishCB = null; 360 private SurfaceControl.Transaction mFinishTransaction = null; 361 362 /** 363 * List of tasks that we are switching away from via this transition. Upon finish, these 364 * pausing tasks will become invisible. 365 * These need to be ordered since the order must be restored if there is no task-switch. 366 */ 367 private ArrayList<TaskState> mPausingTasks = null; 368 369 /** 370 * List of tasks were pausing but closed in a subsequent merged transition. If a 371 * closing task is reopened, the leash is not initially hidden since it is already 372 * visible. 373 */ 374 private ArrayList<TaskState> mClosingTasks = null; 375 376 /** 377 * List of tasks that we are switching to. Upon finish, these will remain visible and 378 * on top. 379 */ 380 private ArrayList<TaskState> mOpeningTasks = null; 381 382 private WindowContainerToken mPipTask = null; 383 private int mPipTaskId = -1; 384 private WindowContainerToken mRecentsTask = null; 385 private int mRecentsTaskId = -1; 386 private TransitionInfo mInfo = null; 387 private boolean mOpeningSeparateHome = false; 388 private boolean mPausingSeparateHome = false; 389 private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; 390 private PictureInPictureSurfaceTransaction mPipTransaction = null; 391 // This is the transition that backs the entire recents transition, and the one that the 392 // pending finish transition below will be merged into 393 private IBinder mTransition = null; 394 private boolean mKeyguardLocked = false; 395 private boolean mWillFinishToHome = false; 396 private Transitions.TransitionHandler mTakeoverHandler = null; 397 398 /** The animation is idle, waiting for the user to choose a task to switch to. */ 399 private static final int STATE_NORMAL = 0; 400 401 /** The user chose a new task to switch to and the animation is animating to it. */ 402 private static final int STATE_NEW_TASK = 1; 403 404 /** The latest state that the recents animation is operating in. */ 405 private int mState = STATE_NORMAL; 406 407 // Snapshots taken when a new display change transition is requested, prior to the display 408 // change being applied. This pending set of snapshots will only be applied when cancel is 409 // next called. 410 private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel; 411 412 // Used to track a pending finish transition, this is only non-null if 413 // enableRecentsBookendTransition() is enabled 414 private IBinder mPendingFinishTransition; 415 private IResultReceiver mPendingRunnerFinishCb; 416 RecentsController(IRecentsAnimationRunner listener)417 RecentsController(IRecentsAnimationRunner listener) { 418 mInstanceId = System.identityHashCode(this); 419 mListener = listener; 420 mDeathHandler = () -> { 421 mExecutor.execute(() -> { 422 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 423 "[%d] RecentsController.DeathRecipient: binder died", mInstanceId); 424 finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */, 425 "deathRecipient"); 426 }); 427 }; 428 try { 429 mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); 430 } catch (RemoteException e) { 431 Slog.e(TAG, "RecentsController: failed to link to death", e); 432 mListener = null; 433 } 434 } 435 436 /** 437 * Sets the started transition for this instance of the recents transition. 438 */ setTransition(IBinder transition)439 void setTransition(IBinder transition) { 440 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 441 "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition); 442 mTransition = transition; 443 } 444 cancel(String reason)445 void cancel(String reason) { 446 // restoring (to-home = false) involves submitting more WM changes, so by default, use 447 // toHome = true when canceling. 448 cancel(true /* toHome */, false /* withScreenshots */, reason); 449 } 450 cancel(boolean toHome, boolean withScreenshots, String reason)451 void cancel(boolean toHome, boolean withScreenshots, String reason) { 452 if (cancelSyntheticTransition(reason)) { 453 return; 454 } 455 456 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 457 "[%d] RecentsController.cancel: toHome=%b reason=%s", 458 mInstanceId, toHome, reason); 459 if (mListener != null) { 460 if (withScreenshots) { 461 sendCancelWithSnapshots(); 462 } else { 463 sendCancel(null, null); 464 } 465 } 466 if (mFinishCB != null) { 467 finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel"); 468 } else { 469 cleanUp(); 470 } 471 } 472 473 /** 474 * Sends a cancel message to the recents animation with snapshots. Used to trigger a 475 * "replace-with-screenshot" like behavior. 476 */ sendCancelWithSnapshots()477 private boolean sendCancelWithSnapshots() { 478 Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null 479 ? mPendingPauseSnapshotsForCancel 480 : getSnapshotsForPausingTasks(); 481 return sendCancel(snapshots.first, snapshots.second); 482 } 483 484 /** 485 * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot. 486 */ getSnapshotsForPausingTasks()487 private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() { 488 int[] taskIds = null; 489 TaskSnapshot[] snapshots = null; 490 if (mPausingTasks != null && mPausingTasks.size() > 0) { 491 taskIds = new int[mPausingTasks.size()]; 492 snapshots = new TaskSnapshot[mPausingTasks.size()]; 493 try { 494 for (int i = 0; i < mPausingTasks.size(); ++i) { 495 TaskState state = mPausingTasks.get(0); 496 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 497 "[%d] RecentsController.sendCancel: Snapshotting task=%d", 498 mInstanceId, state.mTaskInfo.taskId); 499 snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot( 500 state.mTaskInfo.taskId, true /* updateCache */); 501 } 502 } catch (RemoteException e) { 503 taskIds = null; 504 snapshots = null; 505 } 506 } 507 return new Pair(taskIds, snapshots); 508 } 509 510 /** 511 * Sends a cancel message to the recents animation. 512 */ sendCancel(@ullable int[] taskIds, @Nullable TaskSnapshot[] taskSnapshots)513 private boolean sendCancel(@Nullable int[] taskIds, 514 @Nullable TaskSnapshot[] taskSnapshots) { 515 try { 516 final String cancelDetails = taskSnapshots != null ? "with snapshots" : ""; 517 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 518 "[%d] RecentsController.cancel: calling onAnimationCanceled %s", 519 mInstanceId, cancelDetails); 520 mListener.onAnimationCanceled(taskIds, taskSnapshots); 521 return true; 522 } catch (RemoteException e) { 523 Slog.e(TAG, "Error canceling recents animation", e); 524 return false; 525 } 526 } 527 528 /** 529 * Cleans up the recents transition. This should generally not be called directly 530 * to cancel a transition after it has started, instead callers should call one of 531 * the cancel() methods to ensure that Launcher is notified. 532 */ cleanUp()533 void cleanUp() { 534 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 535 "[%d] RecentsController.cleanup", mInstanceId); 536 if (mListener != null && mDeathHandler != null) { 537 mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */); 538 mDeathHandler = null; 539 } 540 mListener = null; 541 mFinishCB = null; 542 // clean-up leash surfacecontrols and anything that might reference them. 543 if (mLeashMap != null) { 544 for (int i = 0; i < mLeashMap.size(); ++i) { 545 mLeashMap.valueAt(i).release(); 546 } 547 mLeashMap = null; 548 } 549 mFinishTransaction = null; 550 mPausingTasks = null; 551 mClosingTasks = null; 552 mOpeningTasks = null; 553 mInfo = null; 554 mTransition = null; 555 mPendingPauseSnapshotsForCancel = null; 556 mPipTaskId = -1; 557 mPipTask = null; 558 mPipTransaction = null; 559 mPendingRunnerFinishCb = null; 560 mPendingFinishTransition = null; 561 mControllers.remove(this); 562 for (int i = 0; i < mStateListeners.size(); i++) { 563 mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING); 564 } 565 } 566 567 /** 568 * Starts a new transition that is not backed by a system transition. 569 */ startSyntheticTransition()570 void startSyntheticTransition() { 571 mTransition = SYNTHETIC_TRANSITION; 572 573 // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as 574 // both opening and closing since there's some pre-existing 575 // dependencies on having a closing task 576 final ActivityManager.RunningTaskInfo homeTask = 577 mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream() 578 .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME) 579 .findFirst() 580 .get(); 581 final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget( 582 homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN, 583 0, true /* isTranslucent */); 584 final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget( 585 homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE, 586 0, true /* isTranslucent */); 587 final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); 588 apps.add(openingTarget); 589 apps.add(closingTarget); 590 try { 591 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 592 "[%d] RecentsController.start: calling onAnimationStart with %d apps", 593 mInstanceId, apps.size()); 594 mListener.onAnimationStart(this, 595 apps.toArray(new RemoteAnimationTarget[apps.size()]), 596 new RemoteAnimationTarget[0], 597 new Rect(0, 0, 0, 0), new Rect(), new Bundle(), 598 null); 599 for (int i = 0; i < mStateListeners.size(); i++) { 600 mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); 601 } 602 } catch (RemoteException e) { 603 Slog.e(TAG, "Error starting recents animation", e); 604 cancel("startSynthetricTransition() failed"); 605 } 606 } 607 608 /** 609 * Returns whether this transition is backed by a real system transition or not. 610 */ isSyntheticTransition()611 boolean isSyntheticTransition() { 612 return mTransition == SYNTHETIC_TRANSITION; 613 } 614 615 /** 616 * Called when a synthetic transition is canceled. 617 */ cancelSyntheticTransition(String reason)618 boolean cancelSyntheticTransition(String reason) { 619 if (!isSyntheticTransition()) { 620 return false; 621 } 622 623 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 624 "[%d] RecentsController.cancelSyntheticTransition: reason=%s", 625 mInstanceId, reason); 626 try { 627 // TODO(b/366021931): Notify the correct tasks once we build actual targets, and 628 // clean up leashes accordingly 629 mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]); 630 } catch (RemoteException e) { 631 Slog.e(TAG, "Error canceling previous recents animation", e); 632 } 633 cleanUp(); 634 return true; 635 } 636 637 /** 638 * Called when a synthetic transition is finished. 639 * @return 640 */ finishSyntheticTransition(IResultReceiver runnerFinishCb, String reason)641 boolean finishSyntheticTransition(IResultReceiver runnerFinishCb, String reason) { 642 if (!isSyntheticTransition()) { 643 return false; 644 } 645 646 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 647 "[%d] RecentsController.finishSyntheticTransition: reason=%s", mInstanceId, 648 reason); 649 if (runnerFinishCb != null) { 650 try { 651 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 652 "[%d] RecentsController.finishInner: calling finish callback", 653 mInstanceId); 654 runnerFinishCb.send(0, null); 655 } catch (RemoteException e) { 656 Slog.e(TAG, "Failed to report transition finished", e); 657 } 658 } 659 // TODO(b/366021931): Clean up leashes accordingly 660 cleanUp(); 661 return true; 662 } 663 start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB)664 boolean start(TransitionInfo info, SurfaceControl.Transaction t, 665 SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { 666 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 667 "[%d] RecentsController.start", mInstanceId); 668 if (mListener == null || mTransition == null) { 669 Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) + 670 " hasTransition=" + (mTransition != null)); 671 cancel("No listener (" + (mListener == null) 672 + ") or no transition (" + (mTransition == null) + ")"); 673 return false; 674 } 675 // First see if this is a valid recents transition. 676 boolean hasPausingTasks = false; 677 for (int i = 0; i < info.getChanges().size(); ++i) { 678 final TransitionInfo.Change change = info.getChanges().get(i); 679 if (TransitionUtil.isWallpaper(change)) continue; 680 if (TransitionUtil.isClosingType(change.getMode())) { 681 hasPausingTasks = true; 682 continue; 683 } 684 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 685 if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { 686 mRecentsTask = taskInfo.token; 687 mRecentsTaskId = taskInfo.taskId; 688 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 689 mRecentsTask = taskInfo.token; 690 mRecentsTaskId = taskInfo.taskId; 691 } 692 } 693 if (mRecentsTask == null && !hasPausingTasks) { 694 // Recents is already running apparently, so this is a no-op. 695 Slog.e(TAG, "Tried to start recents while it is already running."); 696 cancel("No recents task and no pausing tasks"); 697 return false; 698 } 699 700 mInfo = info; 701 mFinishCB = finishCB; 702 mFinishTransaction = finishT; 703 mPausingTasks = new ArrayList<>(); 704 mClosingTasks = new ArrayList<>(); 705 mOpeningTasks = new ArrayList<>(); 706 mLeashMap = new ArrayMap<>(); 707 mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; 708 709 int closingSplitTaskId = INVALID_TASK_ID; 710 final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); 711 final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>(); 712 TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter(); 713 // About layering: we divide up the "layer space" into 3 regions (each the size of 714 // the change count). This lets us categorize things into above/below/between 715 // while maintaining their relative ordering. 716 final int belowLayers = info.getChanges().size(); 717 final int middleLayers = info.getChanges().size() * 2; 718 final int aboveLayers = info.getChanges().size() * 3; 719 720 // Add a background color to each transition root in this transition. 721 if (mBackgroundColor != null) { 722 info.getChanges().stream() 723 .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info)) 724 .distinct() 725 .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash()) 726 .forEach((root) -> createBackgroundSurface(t, root, middleLayers)); 727 } 728 729 for (int i = 0; i < info.getChanges().size(); ++i) { 730 final TransitionInfo.Change change = info.getChanges().get(i); 731 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 732 if (TransitionUtil.isWallpaper(change)) { 733 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 734 // wallpapers go into the "below" layer space 735 belowLayers - i, info, t, mLeashMap); 736 wallpapers.add(target); 737 // Make all the wallpapers opaque since we want them visible from the start 738 t.setAlpha(target.leash, 1); 739 } else if (leafTaskFilter.test(change)) { 740 // start by putting everything into the "below" layer space. 741 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 742 belowLayers - i, info, t, mLeashMap); 743 apps.add(target); 744 if (TransitionUtil.isClosingType(change.getMode())) { 745 mPausingTasks.add(new TaskState(change, target.leash)); 746 closingSplitTaskId = change.getTaskInfo().taskId; 747 if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 748 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 749 " adding pausing leaf home taskId=%d", taskInfo.taskId); 750 // This can only happen if we have a separate recents/home (3p launcher) 751 mPausingSeparateHome = true; 752 } else { 753 final int layer = aboveLayers - i; 754 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 755 " adding pausing leaf taskId=%d at layer=%d", 756 taskInfo.taskId, layer); 757 // raise closing (pausing) task to "above" layer so it isn't covered 758 t.setLayer(target.leash, layer); 759 } 760 if (taskInfo.pictureInPictureParams != null 761 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { 762 mPipTask = taskInfo.token; 763 } 764 } else if (taskInfo != null 765 && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { 766 final int layer = middleLayers - i; 767 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 768 " setting recents activity layer=%d", layer); 769 // There's a 3p launcher, so make sure recents goes above that, but under 770 // the pausing apps. 771 t.setLayer(target.leash, layer); 772 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 773 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 774 " not handling home taskId=%d", taskInfo.taskId); 775 // do nothing 776 } else if (TransitionUtil.isOpeningType(change.getMode())) { 777 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 778 " adding opening leaf taskId=%d", taskInfo.taskId); 779 mOpeningTasks.add(new TaskState(change, target.leash)); 780 } 781 } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) { 782 // Root tasks 783 if (TransitionUtil.isClosingType(change.getMode())) { 784 final int layer = aboveLayers - i; 785 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 786 " adding pausing taskId=%d at layer=%d", taskInfo.taskId, layer); 787 // raise closing (pausing) task to "above" layer so it isn't covered 788 t.setLayer(change.getLeash(), layer); 789 mPausingTasks.add(new TaskState(change, null /* leash */)); 790 } else if (TransitionUtil.isOpeningType(change.getMode())) { 791 final int layer = belowLayers - i; 792 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 793 " adding opening taskId=%d at layer=%d", taskInfo.taskId, layer); 794 // Put into the "below" layer space. 795 t.setLayer(change.getLeash(), layer); 796 mOpeningTasks.add(new TaskState(change, null /* leash */)); 797 } else { 798 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 799 " unhandled root taskId=%d", taskInfo.taskId); 800 } 801 } else if (TransitionUtil.isDividerBar(change) 802 || TransitionUtil.isDimLayer(change)) { 803 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 804 belowLayers - i, info, t, mLeashMap); 805 // Add this as a app and we will separate them on launcher side by window type. 806 apps.add(target); 807 } else { 808 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 809 " unhandled change taskId=%d", 810 taskInfo != null ? taskInfo.taskId : -1); 811 } 812 } 813 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 814 "Applying transaction=%d", t.getId()); 815 t.apply(); 816 817 mTakeoverHandler = mTransitions.getHandlerForTakeover(mTransition, info); 818 819 Bundle b = new Bundle(2 /*capacity*/); 820 b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, 821 mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId)); 822 b.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, mTakeoverHandler != null); 823 try { 824 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 825 "[%d] RecentsController.start: calling onAnimationStart with %d apps", 826 mInstanceId, apps.size()); 827 mListener.onAnimationStart(this, 828 apps.toArray(new RemoteAnimationTarget[apps.size()]), 829 wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), 830 new Rect(0, 0, 0, 0), new Rect(), b, info); 831 for (int i = 0; i < mStateListeners.size(); i++) { 832 mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); 833 } 834 } catch (RemoteException e) { 835 Slog.e(TAG, "Error starting recents animation", e); 836 cancel("onAnimationStart() failed"); 837 } 838 return true; 839 } 840 841 @Override handOffAnimation( RemoteAnimationTarget[] targets, WindowAnimationState[] states)842 public void handOffAnimation( 843 RemoteAnimationTarget[] targets, WindowAnimationState[] states) { 844 mExecutor.execute(() -> { 845 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 846 "[%d] RecentsController.handOffAnimation", mInstanceId); 847 848 if (mTakeoverHandler == null) { 849 Slog.e(TAG, "Tried to hand off an animation without a valid takeover " 850 + "handler."); 851 return; 852 } 853 854 if (targets.length != states.length) { 855 Slog.e(TAG, "Tried to hand off an animation, but the number of targets " 856 + "(" + targets.length + ") doesn't match the number of states " 857 + "(" + states.length + ")"); 858 return; 859 } 860 861 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 862 "[%d] RecentsController.handOffAnimation: got %d states for %d " 863 + "changes", mInstanceId, states.length, mInfo.getChanges().size()); 864 WindowAnimationState[] updatedStates = 865 new WindowAnimationState[mInfo.getChanges().size()]; 866 867 // Ensure that the ordering of animation states is the same as that of matching 868 // changes in mInfo. prefixOrderIndex is set up in reverse order to that of the 869 // changes, so that's what we use to get to the correct ordering. 870 for (int i = 0; i < targets.length; i++) { 871 RemoteAnimationTarget target = targets[i]; 872 updatedStates[updatedStates.length - target.prefixOrderIndex] = states[i]; 873 } 874 875 Transitions.TransitionFinishCallback finishCB = mFinishCB; 876 // Reset the callback here, so any stray calls that aren't coming from the new 877 // handler are ignored. 878 mFinishCB = null; 879 880 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 881 "[%d] RecentsController.handOffAnimation: calling " 882 + "takeOverAnimation with %d states", mInstanceId, 883 updatedStates.length); 884 mTakeoverHandler.takeOverAnimation( 885 mTransition, mInfo, new SurfaceControl.Transaction(), 886 wct -> { 887 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 888 "[%d] RecentsController.handOffAnimation: finish " 889 + "callback", mInstanceId); 890 // Set the callback once again so we can finish correctly. 891 mFinishCB = finishCB; 892 finishInner(true /* toHome */, false /* userLeave */, 893 null /* finishCb */, "takeOverAnimation"); 894 }, updatedStates); 895 }); 896 } 897 898 /** 899 * Updates this controller when a new transition is requested mid-recents transition. 900 */ handleMidTransitionRequest(TransitionRequestInfo request)901 void handleMidTransitionRequest(TransitionRequestInfo request) { 902 if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) { 903 final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange(); 904 if (dispChange.getStartRotation() != dispChange.getEndRotation()) { 905 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 906 "[%d] RecentsController.prepareForMerge: " 907 + "Snapshot due to requested display change", 908 mInstanceId); 909 mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks(); 910 } 911 } 912 } 913 914 /** 915 * Note: because we use a book-end transition to finish the recents transition, we must 916 * either always merge the incoming transition, or always cancel the recents transition 917 * if we don't handle the incoming transition to ensure that the end transition is queued 918 * before any unhandled transitions. 919 */ 920 @SuppressLint("NewApi") merge(TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)921 void merge(TransitionInfo info, SurfaceControl.Transaction startT, 922 SurfaceControl.Transaction finishT, 923 Transitions.TransitionFinishCallback finishCallback) { 924 if (mFinishCB == null) { 925 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 926 "[%d] RecentsController.merge: skip, no finish callback", 927 mInstanceId); 928 // This was no-op'd (likely a repeated start) and we've already completed finish. 929 return; 930 } 931 932 if (Flags.enableRecentsBookendTransition()) { 933 if (info.getType() == TRANSIT_END_RECENTS_TRANSITION) { 934 // This is a pending finish, so merge the end transition to trigger completing 935 // the cleanup of the recents transition 936 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 937 "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION", 938 mInstanceId); 939 consumeMerge(info, startT, finishT, finishCallback); 940 return; 941 } else if (mPendingFinishTransition != null) { 942 // This transition is interrupting a pending finish that was already sent, so 943 // pre-empt the pending finish transition since the state has already changed 944 // in the core 945 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 946 "[%d] RecentsController.merge: Awaiting TRANSIT_END_RECENTS_TRANSITION", 947 mInstanceId); 948 onFinishInner(null /* wct */); 949 return; 950 } 951 } 952 953 if (info.getType() == TRANSIT_SLEEP) { 954 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 955 "[%d] RecentsController.merge: transit_sleep", mInstanceId); 956 // A sleep event means we need to stop animations immediately, so cancel here. 957 cancel("transit_sleep"); 958 return; 959 } 960 if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { 961 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 962 "[%d] RecentsController.merge: keyguard is locked", mInstanceId); 963 // We will not accept new changes if we are swiping over the keyguard. 964 cancel(true /* toHome */, false /* withScreenshots */, "keyguard_locked"); 965 return; 966 } 967 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 968 "[%d] RecentsController.merge", mInstanceId); 969 // Keep all tasks in one list because order matters. 970 ArrayList<TransitionInfo.Change> openingTasks = null; 971 IntArray openingTaskIsLeafs = null; 972 ArrayList<TransitionInfo.Change> closingTasks = null; 973 mOpeningSeparateHome = false; 974 TransitionInfo.Change recentsOpening = null; 975 boolean foundRecentsClosing = false; 976 boolean hasChangingApp = false; 977 final TransitionUtil.LeafTaskFilter leafTaskFilter = 978 new TransitionUtil.LeafTaskFilter(); 979 boolean hasTaskChange = false; 980 for (int i = 0; i < info.getChanges().size(); ++i) { 981 final TransitionInfo.Change change = info.getChanges().get(i); 982 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 983 if (taskInfo != null 984 && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { 985 // Tasks that are always on top (e.g. bubbles), will handle their own transition 986 // as they are on top of everything else. So cancel the merge here. 987 cancel(false /* toHome */, false /* withScreenshots */, 988 "task #" + taskInfo.taskId + " is always_on_top"); 989 return; 990 } 991 if (TransitionUtil.isClosingType(change.getMode()) 992 && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) { 993 // Pinned task is closing as a side effect of the removal of its original Task, 994 // such transition should be handled by PiP. So cancel the merge here. 995 cancel(false /* toHome */, false /* withScreenshots */, 996 "task #" + taskInfo.taskId + " is removed with its original parent"); 997 return; 998 } 999 final boolean isRootTask = taskInfo != null 1000 && TransitionInfo.isIndependent(change, info); 1001 final boolean isRecentsTask = mRecentsTask != null 1002 && mRecentsTask.equals(change.getContainer()); 1003 hasTaskChange = hasTaskChange || isRootTask; 1004 final boolean isLeafTask = leafTaskFilter.test(change); 1005 if (TransitionUtil.isOpeningType(change.getMode()) 1006 || TransitionUtil.isOrderOnly(change)) { 1007 if (isRecentsTask) { 1008 recentsOpening = change; 1009 } else if (isRootTask || isLeafTask) { 1010 if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 1011 // This is usually a 3p launcher 1012 mOpeningSeparateHome = true; 1013 } 1014 if (openingTasks == null) { 1015 openingTasks = new ArrayList<>(); 1016 openingTaskIsLeafs = new IntArray(); 1017 } 1018 openingTasks.add(change); 1019 openingTaskIsLeafs.add(isLeafTask ? 1 : 0); 1020 } 1021 } else if (TransitionUtil.isClosingType(change.getMode())) { 1022 if (isRecentsTask) { 1023 foundRecentsClosing = true; 1024 } else if (isRootTask || isLeafTask) { 1025 if (closingTasks == null) { 1026 closingTasks = new ArrayList<>(); 1027 } 1028 closingTasks.add(change); 1029 } 1030 } else if (change.getMode() == TRANSIT_CHANGE) { 1031 // Finish recents animation if the display is changed, so the default 1032 // transition handler can play the animation such as rotation effect. 1033 if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) 1034 && info.getType() == TRANSIT_CHANGE) { 1035 // This call to cancel will use the screenshots taken preemptively in 1036 // handleMidTransitionRequest() prior to the display changing 1037 cancel(mWillFinishToHome, true /* withScreenshots */, "display change"); 1038 return; 1039 } 1040 // Don't consider order-only & non-leaf changes as changing apps. 1041 if (!TransitionUtil.isOrderOnly(change) && isLeafTask) { 1042 hasChangingApp = true; 1043 // Check if the changing app is moving to top and fullscreen. This handles 1044 // the case where we moved from desktop to recents and launching a desktop 1045 // task in fullscreen. 1046 if ((change.getFlags() & FLAG_MOVED_TO_TOP) != 0 1047 && taskInfo != null 1048 && taskInfo.getWindowingMode() 1049 == WINDOWING_MODE_FULLSCREEN) { 1050 if (openingTasks == null) { 1051 openingTasks = new ArrayList<>(); 1052 openingTaskIsLeafs = new IntArray(); 1053 } 1054 openingTasks.add(change); 1055 openingTaskIsLeafs.add(1); 1056 } 1057 } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME 1058 && !isRecentsTask ) { 1059 // Unless it is a 3p launcher. This means that the 3p launcher was already 1060 // visible (eg. the "pausing" task is translucent over the 3p launcher). 1061 // Treat it as if we are "re-opening" the 3p launcher. 1062 if (openingTasks == null) { 1063 openingTasks = new ArrayList<>(); 1064 openingTaskIsLeafs = new IntArray(); 1065 } 1066 openingTasks.add(change); 1067 openingTaskIsLeafs.add(1); 1068 } 1069 } 1070 } 1071 if (hasChangingApp && foundRecentsClosing) { 1072 // This happens when a visible app is expanding (usually PiP). In this case, 1073 // that transition probably has a special-purpose animation, so finish recents 1074 // now and let it do its animation (since recents is going to be occluded). 1075 sendCancelWithSnapshots(); 1076 mExecutor.executeDelayed( 1077 () -> finishInner(true /* toHome */, false /* userLeaveHint */, 1078 null /* finishCb */, "merge"), 0); 1079 return; 1080 } 1081 if (recentsOpening != null) { 1082 // the recents task re-appeared. This happens if the user gestures before the 1083 // task-switch (NEW_TASK) animation finishes. 1084 if (mState == STATE_NORMAL) { 1085 Slog.e(TAG, "Returning to recents while recents is already idle."); 1086 } 1087 if (closingTasks == null || closingTasks.size() == 0) { 1088 Slog.e(TAG, "Returning to recents without closing any opening tasks."); 1089 } 1090 // Setup may hide it initially since it doesn't know that overview was still active. 1091 startT.show(recentsOpening.getLeash()); 1092 startT.setAlpha(recentsOpening.getLeash(), 1.f); 1093 mState = STATE_NORMAL; 1094 } 1095 boolean didMergeThings = false; 1096 if (closingTasks != null) { 1097 // Potentially cancelling a task-switch. Move the tasks back to mPausing if they 1098 // are in mOpening. 1099 for (int i = 0; i < closingTasks.size(); ++i) { 1100 final TransitionInfo.Change change = closingTasks.get(i); 1101 final int pausingIdx = TaskState.indexOf(mPausingTasks, change); 1102 if (pausingIdx >= 0) { 1103 // We are closing the pausing task, but it is still visible and can be 1104 // restart by another transition prior to this transition finishing 1105 final TaskState closingTask = mPausingTasks.remove(pausingIdx); 1106 mClosingTasks.add(closingTask); 1107 didMergeThings = true; 1108 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1109 " closing pausing taskId=%d", change.getTaskInfo().taskId); 1110 continue; 1111 } 1112 int openingIdx = TaskState.indexOf(mOpeningTasks, change); 1113 if (openingIdx < 0) { 1114 Slog.w(TAG, "Closing a task that wasn't opening, this may be split or" 1115 + " something unexpected: " + change.getTaskInfo().taskId); 1116 continue; 1117 } 1118 final TaskState openingTask = mOpeningTasks.remove(openingIdx); 1119 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1120 " pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "", 1121 openingTask.mTaskInfo.taskId); 1122 mPausingTasks.add(openingTask); 1123 didMergeThings = true; 1124 } 1125 } 1126 RemoteAnimationTarget[] appearedTargets = null; 1127 if (openingTasks != null && openingTasks.size() > 0) { 1128 // Switching to some new tasks, add to mOpening and remove from mPausing. Also, 1129 // enter NEW_TASK state since this will start the switch-to animation. 1130 final int layer = mInfo.getChanges().size() * 3; 1131 int openingLeafCount = 0; 1132 for (int i = 0; i < openingTaskIsLeafs.size(); ++i) { 1133 openingLeafCount += openingTaskIsLeafs.get(i); 1134 } 1135 if (openingLeafCount > 0) { 1136 appearedTargets = new RemoteAnimationTarget[openingLeafCount]; 1137 } 1138 boolean onlyOpeningPausedTasks = true; 1139 int nextTargetIdx = 0; 1140 for (int i = 0; i < openingTasks.size(); ++i) { 1141 final TransitionInfo.Change change = openingTasks.get(i); 1142 final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; 1143 final int closingIdx = TaskState.indexOf(mClosingTasks, change); 1144 if (closingIdx >= 0) { 1145 // Remove opening tasks from closing set 1146 mClosingTasks.remove(closingIdx); 1147 } 1148 final int pausingIdx = TaskState.indexOf(mPausingTasks, change); 1149 if (pausingIdx >= 0) { 1150 // Something is showing/opening a previously-pausing app. 1151 if (isLeaf) { 1152 appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget( 1153 change, layer, mPausingTasks.get(pausingIdx).mLeash); 1154 } 1155 final TaskState pausingTask = mPausingTasks.remove(pausingIdx); 1156 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1157 " opening pausing %staskId=%d", isLeaf ? "leaf " : "", 1158 pausingTask.mTaskInfo.taskId); 1159 mOpeningTasks.add(pausingTask); 1160 // Setup hides opening tasks initially, so make it visible again (since we 1161 // are already showing it). 1162 startT.show(change.getLeash()); 1163 startT.setAlpha(change.getLeash(), 1.f); 1164 } else if (isLeaf) { 1165 // We are receiving new opening leaf tasks, so convert to onTasksAppeared. 1166 final RemoteAnimationTarget target = TransitionUtil.newTarget( 1167 change, layer, info, startT, mLeashMap); 1168 appearedTargets[nextTargetIdx++] = target; 1169 // reparent into the original `mInfo` since that's where we are animating. 1170 final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo); 1171 final boolean wasClosing = closingIdx >= 0; 1172 startT.reparent(target.leash, root.getLeash()); 1173 startT.setPosition(target.leash, 1174 change.getStartAbsBounds().left - root.getOffset().x, 1175 change.getStartAbsBounds().top - root.getOffset().y); 1176 startT.setLayer(target.leash, layer); 1177 if (wasClosing) { 1178 // App was previously visible and is closing 1179 startT.show(target.leash); 1180 startT.setAlpha(target.leash, 1f); 1181 // Also override the task alpha as it was set earlier when dispatching 1182 // the transition and setting up the leash to hide the 1183 startT.setAlpha(change.getLeash(), 1f); 1184 } else { 1185 // Hide the animation leash, let the listener show it 1186 startT.hide(target.leash); 1187 } 1188 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1189 " opening new leaf taskId=%d wasClosing=%b", 1190 target.taskId, wasClosing); 1191 mOpeningTasks.add(new TaskState(change, target.leash)); 1192 onlyOpeningPausedTasks = false; 1193 } else { 1194 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1195 " opening new taskId=%d", change.getTaskInfo().taskId); 1196 startT.setLayer(change.getLeash(), layer); 1197 // Setup hides opening tasks initially, so make it visible since recents 1198 // is only animating the leafs. 1199 startT.show(change.getLeash()); 1200 mOpeningTasks.add(new TaskState(change, null)); 1201 onlyOpeningPausedTasks = false; 1202 } 1203 } 1204 didMergeThings = true; 1205 if (!onlyOpeningPausedTasks) { 1206 // If we are only opening paused leaf tasks, then we aren't actually quick 1207 // switching or launching a new task from overview, and if Launcher requests to 1208 // finish(toHome=false) as a response to the pausing tasks being opened again, 1209 // we should allow that to be considered returningToApp 1210 mState = STATE_NEW_TASK; 1211 } 1212 } 1213 if (mPausingTasks.isEmpty()) { 1214 // The pausing tasks may be removed by the incoming closing tasks. 1215 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1216 "[%d] RecentsController.merge: empty pausing tasks", mInstanceId); 1217 } 1218 if (!hasTaskChange) { 1219 // Activity only transition, so consume the merge as it doesn't affect the rest of 1220 // recents. 1221 Slog.d(TAG, "Got an activity only transition during recents, so apply directly"); 1222 mergeActivityOnly(info, startT); 1223 } else if (!didMergeThings) { 1224 // Didn't recognize anything in incoming transition so don't merge it. 1225 Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing=" 1226 + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId); 1227 if (foundRecentsClosing || mRecentsTaskId < 0) { 1228 mWillFinishToHome = false; 1229 cancel(false /* toHome */, false /* withScreenshots */, "didn't merge"); 1230 } 1231 return; 1232 } 1233 1234 // At this point, we are accepting the merge. 1235 consumeMerge(info, startT, finishT, finishCallback); 1236 1237 // Notify Launcher of the new opening tasks if necessary 1238 boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(); 1239 if (appearedTargets != null) { 1240 try { 1241 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1242 "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId); 1243 mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null); 1244 } catch (RemoteException e) { 1245 Slog.e(TAG, "Error sending appeared tasks to recents animation", e); 1246 } 1247 } 1248 } 1249 1250 /** 1251 * Consumes the merge of the other given transition. 1252 */ consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)1253 private void consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT, 1254 SurfaceControl.Transaction finishT, 1255 Transitions.TransitionFinishCallback finishCallback) { 1256 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1257 "[%d] RecentsController.merge: consuming merge", 1258 mInstanceId); 1259 1260 startT.apply(); 1261 // Since we're accepting the merge, update the finish transaction so that changes via 1262 // that transaction will be applied on top of those of the merged transitions 1263 mFinishTransaction = finishT; 1264 boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(); 1265 if (!passTransitionInfo) { 1266 // not using the incoming anim-only surfaces 1267 info.releaseAnimSurfaces(); 1268 } 1269 finishCallback.onTransitionFinished(null /* wct */); 1270 } 1271 1272 /** For now, just set-up a jump-cut to the new activity. */ mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t)1273 private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) { 1274 for (int i = 0; i < info.getChanges().size(); ++i) { 1275 final TransitionInfo.Change change = info.getChanges().get(i); 1276 if (TransitionUtil.isOpeningType(change.getMode())) { 1277 t.show(change.getLeash()); 1278 t.setAlpha(change.getLeash(), 1.f); 1279 } else if (TransitionUtil.isClosingType(change.getMode())) { 1280 t.hide(change.getLeash()); 1281 } 1282 } 1283 } 1284 1285 @Override setInputConsumerEnabled(boolean enabled)1286 public void setInputConsumerEnabled(boolean enabled) { 1287 mExecutor.execute(() -> { 1288 if (mFinishCB == null || !enabled) { 1289 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1290 "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b", 1291 mFinishCB != null, enabled); 1292 return; 1293 } 1294 final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId() 1295 : DEFAULT_DISPLAY; 1296 // transient launches don't receive focus automatically. Since we are taking over 1297 // the gesture now, take focus explicitly. 1298 // This also moves recents back to top if the user gestured before a switch 1299 // animation finished. 1300 try { 1301 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1302 "[%d] RecentsController.setInputConsumerEnabled: set focus to recents", 1303 mInstanceId); 1304 ActivityTaskManager.getService().focusTopTask(displayId); 1305 } catch (RemoteException e) { 1306 Slog.e(TAG, "Failed to set focused task", e); 1307 } 1308 }); 1309 } 1310 1311 @Override setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)1312 public void setFinishTaskTransaction(int taskId, 1313 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { 1314 mExecutor.execute(() -> { 1315 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1316 "[%d] RecentsController.setFinishTaskTransaction: taskId=%d," 1317 + " [mFinishCB is non-null]=%b", 1318 mInstanceId, taskId, mFinishCB != null); 1319 if (mFinishCB == null) return; 1320 mPipTransaction = finishTransaction; 1321 mPipTaskId = taskId; 1322 }); 1323 } 1324 1325 @Override 1326 @SuppressLint("NewApi") finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb)1327 public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) { 1328 mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb, 1329 "requested")); 1330 } 1331 1332 /** 1333 * @param runnerFinishCb The remote finish callback to run after finish is complete, this is 1334 * not the same as mFinishCb which reports the transition is finished 1335 * to WM. 1336 */ finishInner(boolean toHome, boolean sendUserLeaveHint, IResultReceiver runnerFinishCb, String reason)1337 private void finishInner(boolean toHome, boolean sendUserLeaveHint, 1338 IResultReceiver runnerFinishCb, String reason) { 1339 if (finishSyntheticTransition(runnerFinishCb, reason)) { 1340 return; 1341 } 1342 1343 if (mFinishCB == null || (Flags.enableRecentsBookendTransition() 1344 && mPendingFinishTransition != null)) { 1345 Slog.e(TAG, "Duplicate call to finish"); 1346 if (runnerFinishCb != null) { 1347 try { 1348 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1349 "[%d] RecentsController.finishInner: calling finish callback", 1350 mInstanceId); 1351 runnerFinishCb.send(0, null); 1352 } catch (RemoteException e) { 1353 Slog.e(TAG, "Failed to report transition finished", e); 1354 } 1355 } 1356 return; 1357 } 1358 1359 boolean returningToApp = !toHome 1360 && !mWillFinishToHome 1361 && mPausingTasks != null 1362 && mState == STATE_NORMAL; 1363 if (!Flags.enableRecentsBookendTransition()) { 1364 // This is only necessary when the recents transition is finished using a finishWCT, 1365 // otherwise a new transition will notify the relevant observers 1366 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { 1367 mHomeTransitionObserver.notifyHomeVisibilityChanged(true); 1368 } else if (!toHome && mState == STATE_NEW_TASK 1369 && allAppsAreTranslucent(mOpeningTasks)) { 1370 // We are opening a translucent app. Launcher is still visible so we do nothing. 1371 } else if (!toHome) { 1372 // For some transitions, we may have notified home activity that it became 1373 // visible. We need to notify the observer that we are no longer going home. 1374 mHomeTransitionObserver.notifyHomeVisibilityChanged(false); 1375 } 1376 } 1377 1378 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1379 "[%d] RecentsController.finishInner: toHome=%b userLeave=%b " 1380 + "willFinishToHome=%b state=%d hasPausingTasks=%b reason=%s", 1381 mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState, 1382 mPausingTasks != null, reason); 1383 1384 final SurfaceControl.Transaction t = mFinishTransaction; 1385 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1386 1387 // The following code must set this if it is changing anything in core that might affect 1388 // transitions as a part of finishing the recents transition 1389 boolean requiresBookendTransition = false; 1390 1391 if (mKeyguardLocked && mRecentsTask != null) { 1392 if (toHome) wct.reorder(mRecentsTask, true /* toTop */); 1393 else wct.restoreTransientOrder(mRecentsTask); 1394 // We are manipulating the window hierarchy, which should only be done with the 1395 // bookend transition 1396 requiresBookendTransition = true; 1397 } 1398 if (returningToApp) { 1399 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); 1400 // The gesture is returning to the pausing-task(s) rather than continuing with 1401 // recents, so end the transition by moving the app back to the top (and also 1402 // re-showing it's task). 1403 for (int i = mPausingTasks.size() - 1; i >= 0; --i) { 1404 // reverse order so that index 0 ends up on top 1405 wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); 1406 t.show(mPausingTasks.get(i).mTaskSurface); 1407 } 1408 setCornerRadiusForFreeformTasks( 1409 mRecentTasksController.getContext(), t, mPausingTasks); 1410 if (!mKeyguardLocked && mRecentsTask != null) { 1411 wct.restoreTransientOrder(mRecentsTask); 1412 } 1413 // We are manipulating the window hierarchy, which should only be done with the 1414 // bookend transition 1415 requiresBookendTransition = true; 1416 } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { 1417 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " 3p launching home"); 1418 // Special situation where 3p launcher was changed during recents (this happens 1419 // during tapltests...). Here we get both "return to home" AND "home opening". 1420 // This is basically going home, but we have to restore the recents and home order. 1421 for (int i = 0; i < mOpeningTasks.size(); ++i) { 1422 final TaskState state = mOpeningTasks.get(i); 1423 if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 1424 // Make sure it is on top. 1425 wct.reorder(state.mToken, true /* onTop */); 1426 } 1427 t.show(state.mTaskSurface); 1428 } 1429 for (int i = mPausingTasks.size() - 1; i >= 0; --i) { 1430 t.hide(mPausingTasks.get(i).mTaskSurface); 1431 } 1432 if (!mKeyguardLocked && mRecentsTask != null) { 1433 wct.restoreTransientOrder(mRecentsTask); 1434 } 1435 // We are manipulating the window hierarchy, which should only be done with the 1436 // bookend transition 1437 requiresBookendTransition = true; 1438 } else { 1439 if (mPausingSeparateHome) { 1440 if (mOpeningTasks.isEmpty()) { 1441 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1442 " recents occluded 3p home"); 1443 } else { 1444 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1445 " switch task by recents on 3p home"); 1446 } 1447 } 1448 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); 1449 // The general case: committing to recents, going home, or switching tasks. 1450 for (int i = 0; i < mOpeningTasks.size(); ++i) { 1451 t.show(mOpeningTasks.get(i).mTaskSurface); 1452 } 1453 setCornerRadiusForFreeformTasks( 1454 mRecentTasksController.getContext(), t, mOpeningTasks); 1455 for (int i = 0; i < mPausingTasks.size(); ++i) { 1456 cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint); 1457 } 1458 for (int i = 0; i < mClosingTasks.size(); ++i) { 1459 cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint); 1460 } 1461 1462 if (mPipTransaction != null && sendUserLeaveHint) { 1463 SurfaceControl pipLeash = null; 1464 TransitionInfo.Change pipChange = null; 1465 if (mPipTask != null) { 1466 pipChange = mInfo.getChange(mPipTask); 1467 pipLeash = pipChange.getLeash(); 1468 } else if (mPipTaskId != -1) { 1469 // find a task with taskId from #setFinishTaskTransaction() 1470 for (TransitionInfo.Change change : mInfo.getChanges()) { 1471 if (change.getTaskInfo() != null 1472 && change.getTaskInfo().taskId == mPipTaskId) { 1473 pipChange = change; 1474 pipLeash = change.getLeash(); 1475 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1476 "RecentsController.finishInner:" 1477 + " found a change with taskId=%d", mPipTaskId); 1478 } 1479 } 1480 } 1481 if (pipLeash == null) { 1482 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1483 "RecentsController.finishInner: no valid PiP leash;" 1484 + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d", 1485 mPipTransaction, mPipTask, mPipTaskId); 1486 } else { 1487 t.show(pipLeash); 1488 PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t); 1489 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1490 "RecentsController.finishInner: PiP transaction %s merged", 1491 mPipTransaction); 1492 if (PipUtils.isPip2ExperimentEnabled()) { 1493 // If this path is triggered, we are in auto-enter PiP flow in gesture 1494 // navigation mode, which means "Recents" transition should be followed 1495 // by a TRANSIT_PIP. Hence, we take the WCT was about to be sent 1496 // to Core to be applied during finishTransition(), we modify it to 1497 // factor in PiP changes, and we send it as a direct startWCT for 1498 // a new TRANSIT_PIP type transition. Recents still sends 1499 // finishTransition() to update visibilities, but with finishWCT=null. 1500 TransitionRequestInfo requestInfo = new TransitionRequestInfo( 1501 TRANSIT_PIP, null /* triggerTask */, pipChange.getTaskInfo(), 1502 null /* remote */, null /* displayChange */, 0 /* flags */); 1503 // Use mTransition IBinder token temporarily just to get PipTransition 1504 // to return from its handleRequest(). The actual TRANSIT_PIP will have 1505 // anew token once it arrives into PipTransition#startAnimation(). 1506 Pair<Transitions.TransitionHandler, WindowContainerTransaction> 1507 requestRes = mTransitions.dispatchRequest(mTransition, 1508 requestInfo, null /* skip */); 1509 wct.merge(requestRes.second, true); 1510 mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */); 1511 // We need to clear the WCT to send finishWCT=null for Recents. 1512 wct.clear(); 1513 1514 if (Flags.enableRecentsBookendTransition()) { 1515 // Notify the mixers of the pending finish 1516 for (int i = 0; i < mMixers.size(); ++i) { 1517 mMixers.get(i).handleFinishRecents(returningToApp, wct, t); 1518 } 1519 1520 // In this case, we've already started the PIP transition, so we can 1521 // clean up immediately 1522 mPendingRunnerFinishCb = runnerFinishCb; 1523 onFinishInner(null); 1524 return; 1525 } 1526 } 1527 } 1528 } 1529 } 1530 1531 // Notify the mixers of the pending finish 1532 for (int i = 0; i < mMixers.size(); ++i) { 1533 mMixers.get(i).handleFinishRecents(returningToApp, wct, t); 1534 } 1535 1536 if (Flags.enableRecentsBookendTransition()) { 1537 if (!wct.isEmpty()) { 1538 if (requiresBookendTransition) { 1539 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1540 "[%d] RecentsController.finishInner: " 1541 + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId); 1542 mPendingRunnerFinishCb = runnerFinishCb; 1543 mPendingFinishTransition = mTransitions.startTransition( 1544 TRANSIT_END_RECENTS_TRANSITION, wct, 1545 new PendingFinishTransitionHandler()); 1546 } else { 1547 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1548 "[%d] RecentsController.finishInner: Non-transition affecting wct", 1549 mInstanceId); 1550 mPendingRunnerFinishCb = runnerFinishCb; 1551 onFinishInner(wct); 1552 } 1553 } else { 1554 // If there's no work to do, just go ahead and clean up 1555 mPendingRunnerFinishCb = runnerFinishCb; 1556 onFinishInner(null /* wct */); 1557 } 1558 } else { 1559 mPendingRunnerFinishCb = runnerFinishCb; 1560 onFinishInner(wct); 1561 } 1562 } 1563 1564 /** 1565 * Runs the actual logic to finish the recents transition. 1566 */ onFinishInner(@ullable WindowContainerTransaction wct)1567 private void onFinishInner(@Nullable WindowContainerTransaction wct) { 1568 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1569 "[%d] RecentsController.finishInner: Completing finish", mInstanceId); 1570 final Transitions.TransitionFinishCallback finishCb = mFinishCB; 1571 final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb; 1572 1573 cleanUp(); 1574 finishCb.onTransitionFinished(wct); 1575 if (runnerFinishCb != null) { 1576 try { 1577 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1578 "[%d] RecentsController.finishInner: calling finish callback", 1579 mInstanceId); 1580 runnerFinishCb.send(0, null); 1581 } catch (RemoteException e) { 1582 Slog.e(TAG, "Failed to report transition finished", e); 1583 } 1584 } 1585 } 1586 setCornerRadiusForFreeformTasks( Context context, SurfaceControl.Transaction t, ArrayList<TaskState> tasks)1587 private static void setCornerRadiusForFreeformTasks( 1588 Context context, 1589 SurfaceControl.Transaction t, 1590 ArrayList<TaskState> tasks) { 1591 if (!ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) { 1592 return; 1593 } 1594 int cornerRadius = getCornerRadius(context); 1595 for (int i = 0; i < tasks.size(); ++i) { 1596 TaskState task = tasks.get(i); 1597 if (task.mTaskInfo != null && task.mTaskInfo.isFreeform()) { 1598 t.setCornerRadius(task.mTaskSurface, cornerRadius); 1599 } 1600 } 1601 } 1602 getCornerRadius(Context context)1603 private static int getCornerRadius(Context context) { 1604 return context.getResources().getDimensionPixelSize( 1605 R.dimen.desktop_windowing_freeform_rounded_corner_radius); 1606 } 1607 allAppsAreTranslucent(ArrayList<TaskState> tasks)1608 private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) { 1609 if (tasks == null) { 1610 return false; 1611 } 1612 for (int i = tasks.size() - 1; i >= 0; --i) { 1613 if (!tasks.get(i).mIsTranslucent) { 1614 return false; 1615 } 1616 } 1617 return true; 1618 } 1619 createBackgroundSurface(SurfaceControl.Transaction transaction, SurfaceControl parent, int layer)1620 private void createBackgroundSurface(SurfaceControl.Transaction transaction, 1621 SurfaceControl parent, int layer) { 1622 if (mBackgroundColor == null) { 1623 return; 1624 } 1625 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1626 " adding background color to layer=%d", layer); 1627 final SurfaceControl background = new SurfaceControl.Builder() 1628 .setName("recents_background") 1629 .setColorLayer() 1630 .setOpaque(true) 1631 .setParent(parent) 1632 .build(); 1633 transaction.setColor(background, colorToFloatArray(mBackgroundColor)); 1634 transaction.setLayer(background, layer); 1635 transaction.setAlpha(background, 1F); 1636 transaction.show(background); 1637 } 1638 colorToFloatArray(@onNull Color color)1639 private static float[] colorToFloatArray(@NonNull Color color) { 1640 return new float[]{color.red(), color.green(), color.blue()}; 1641 } 1642 cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint)1643 private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, 1644 SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { 1645 if (!sendUserLeaveHint && task.isLeaf()) { 1646 // This means recents is not *actually* finishing, so of course we gotta 1647 // do special stuff in WMCore to accommodate. 1648 wct.setDoNotPip(task.mToken); 1649 } 1650 // Since we will reparent out of the leashes, pre-emptively hide the child 1651 // surface to match the leash. Otherwise, there will be a flicker before the 1652 // visibility gets committed in Core when using split-screen (in splitscreen, 1653 // the leaf-tasks are not "independent" so aren't hidden by normal setup). 1654 finishTransaction.hide(task.mTaskSurface); 1655 } 1656 1657 @Override setWillFinishToHome(boolean willFinishToHome)1658 public void setWillFinishToHome(boolean willFinishToHome) { 1659 mExecutor.execute(() -> { 1660 mWillFinishToHome = willFinishToHome; 1661 }); 1662 } 1663 1664 /** 1665 * @see IRecentsAnimationController#detachNavigationBarFromApp 1666 */ 1667 @Override detachNavigationBarFromApp(boolean moveHomeToTop)1668 public void detachNavigationBarFromApp(boolean moveHomeToTop) { 1669 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1670 "[%d] RecentsController.detachNavigationBarFromApp", mInstanceId); 1671 mExecutor.execute(() -> { 1672 if (mTransition == null) return; 1673 try { 1674 ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition); 1675 } catch (RemoteException e) { 1676 Slog.e(TAG, "Failed to detach the navigation bar from app", e); 1677 } 1678 }); 1679 } 1680 1681 /** 1682 * A temporary transition handler used with the pending finish transition, which runs the 1683 * cleanup/finish logic once the pending transition is merged/handled. 1684 * This is only initialized if Flags.enableRecentsBookendTransition() is enabled. 1685 */ 1686 private class PendingFinishTransitionHandler implements Transitions.TransitionHandler { 1687 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)1688 public boolean startAnimation(@NonNull IBinder transition, 1689 @NonNull TransitionInfo info, 1690 @NonNull SurfaceControl.Transaction startTransaction, 1691 @NonNull SurfaceControl.Transaction finishTransaction, 1692 @NonNull Transitions.TransitionFinishCallback finishCallback) { 1693 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1694 "[%d] PendingFinishTransitionHandler.startAnimation: " 1695 + "Started pending finish transition", mInstanceId); 1696 return false; 1697 } 1698 1699 @Nullable 1700 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)1701 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 1702 @NonNull TransitionRequestInfo request) { 1703 return null; 1704 } 1705 1706 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction)1707 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 1708 @Nullable SurfaceControl.Transaction finishTransaction) { 1709 if (mPendingFinishTransition == null) { 1710 // The cleanup was pre-empted by an earlier transition, nothing there is nothing 1711 // to do here 1712 return; 1713 } 1714 // Once we have merged (or not if the WCT didn't result in any changes), then we can 1715 // run the pending finish logic 1716 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1717 "[%d] PendingFinishTransitionHandler.onTransitionConsumed: " 1718 + "Consumed pending finish transition", mInstanceId); 1719 onFinishInner(null /* wct */); 1720 } 1721 }; 1722 }; 1723 1724 /** Utility class to track the state of a task as-seen by recents. */ 1725 private static class TaskState { 1726 WindowContainerToken mToken; 1727 ActivityManager.RunningTaskInfo mTaskInfo; 1728 1729 /** The surface/leash of the task provided by Core. */ 1730 SurfaceControl mTaskSurface; 1731 1732 /** True when the task is translucent. */ 1733 final boolean mIsTranslucent; 1734 1735 /** The (local) animation-leash created for this task. Only non-null for leafs. */ 1736 @Nullable 1737 SurfaceControl mLeash; 1738 TaskState(TransitionInfo.Change change, SurfaceControl leash)1739 TaskState(TransitionInfo.Change change, SurfaceControl leash) { 1740 mToken = change.getContainer(); 1741 mTaskInfo = change.getTaskInfo(); 1742 mTaskSurface = change.getLeash(); 1743 mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; 1744 mLeash = leash; 1745 } 1746 indexOf(ArrayList<TaskState> list, TransitionInfo.Change change)1747 static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) { 1748 for (int i = list.size() - 1; i >= 0; --i) { 1749 if (list.get(i).mToken.equals(change.getContainer())) { 1750 return i; 1751 } 1752 } 1753 return -1; 1754 } 1755 isLeaf()1756 boolean isLeaf() { 1757 return mLeash != null; 1758 } 1759 toString()1760 public String toString() { 1761 return "" + mToken + " : " + mLeash; 1762 } 1763 } 1764 1765 /** 1766 * An interface for a mixed handler to receive information about recents requests (since these 1767 * come into this handler directly vs from WMCore request). 1768 */ 1769 public interface RecentsMixedHandler extends Transitions.TransitionHandler { 1770 /** 1771 * Called when a recents request comes in. If the handler wants to "accept" the transition, 1772 * it should return a Consumer accepting the IBinder for the transition. If not, it should 1773 * return `null`. 1774 * 1775 * If a mixed-handler accepts this recents, it will be the de-facto handler for this 1776 * transition and is required to call the associated {@link #startAnimation}, 1777 * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. 1778 */ 1779 @Nullable handleRecentsRequest()1780 Consumer<IBinder> handleRecentsRequest(); 1781 1782 /** 1783 * Called when a recents transition has finished, with a WCT and SurfaceControl Transaction 1784 * that can be used to add to any changes needed to restore the state. 1785 */ handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, @NonNull SurfaceControl.Transaction finishT)1786 void handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, 1787 @NonNull SurfaceControl.Transaction finishT); 1788 } 1789 } 1790