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.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; 23 import static android.view.WindowManager.TRANSIT_CHANGE; 24 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 25 import static android.view.WindowManager.TRANSIT_PIP; 26 import static android.view.WindowManager.TRANSIT_SLEEP; 27 import static android.view.WindowManager.TRANSIT_TO_FRONT; 28 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 29 30 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; 31 import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; 32 33 import android.annotation.Nullable; 34 import android.annotation.SuppressLint; 35 import android.app.ActivityManager; 36 import android.app.ActivityTaskManager; 37 import android.app.IApplicationThread; 38 import android.app.PendingIntent; 39 import android.content.Intent; 40 import android.graphics.Color; 41 import android.graphics.Rect; 42 import android.os.Bundle; 43 import android.os.IBinder; 44 import android.os.RemoteException; 45 import android.util.ArrayMap; 46 import android.util.IntArray; 47 import android.util.Pair; 48 import android.util.Slog; 49 import android.view.Display; 50 import android.view.IRecentsAnimationController; 51 import android.view.IRecentsAnimationRunner; 52 import android.view.RemoteAnimationTarget; 53 import android.view.SurfaceControl; 54 import android.window.PictureInPictureSurfaceTransaction; 55 import android.window.TaskSnapshot; 56 import android.window.TransitionInfo; 57 import android.window.TransitionRequestInfo; 58 import android.window.WindowAnimationState; 59 import android.window.WindowContainerToken; 60 import android.window.WindowContainerTransaction; 61 62 import androidx.annotation.NonNull; 63 64 import com.android.internal.annotations.VisibleForTesting; 65 import com.android.internal.os.IResultReceiver; 66 import com.android.internal.protolog.common.ProtoLog; 67 import com.android.wm.shell.common.ShellExecutor; 68 import com.android.wm.shell.common.pip.PipUtils; 69 import com.android.wm.shell.protolog.ShellProtoLogGroup; 70 import com.android.wm.shell.shared.TransitionUtil; 71 import com.android.wm.shell.sysui.ShellInit; 72 import com.android.wm.shell.transition.HomeTransitionObserver; 73 import com.android.wm.shell.transition.Transitions; 74 75 import java.util.ArrayList; 76 import java.util.function.Consumer; 77 78 /** 79 * Handles the Recents (overview) animation. Only one of these can run at a time. A recents 80 * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored. 81 */ 82 public class RecentsTransitionHandler implements Transitions.TransitionHandler { 83 private static final String TAG = "RecentsTransitionHandler"; 84 85 private final Transitions mTransitions; 86 private final ShellExecutor mExecutor; 87 @Nullable 88 private final RecentTasksController mRecentTasksController; 89 private IApplicationThread mAnimApp = null; 90 private final ArrayList<RecentsController> mControllers = new ArrayList<>(); 91 private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>(); 92 93 /** 94 * List of other handlers which might need to mix recents with other things. These are checked 95 * in the order they are added. Ideally there should only be one. 96 */ 97 private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); 98 99 private final HomeTransitionObserver mHomeTransitionObserver; 100 private @Nullable Color mBackgroundColor; 101 RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, HomeTransitionObserver homeTransitionObserver)102 public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, 103 @Nullable RecentTasksController recentTasksController, 104 HomeTransitionObserver homeTransitionObserver) { 105 mTransitions = transitions; 106 mExecutor = transitions.getMainExecutor(); 107 mRecentTasksController = recentTasksController; 108 mHomeTransitionObserver = homeTransitionObserver; 109 if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; 110 if (recentTasksController == null) return; 111 shellInit.addInitCallback(() -> { 112 recentTasksController.setTransitionHandler(this); 113 transitions.addHandler(this); 114 }, this); 115 } 116 117 /** Register a mixer handler. {@see RecentsMixedHandler}*/ addMixer(RecentsMixedHandler mixer)118 public void addMixer(RecentsMixedHandler mixer) { 119 mMixers.add(mixer); 120 } 121 122 /** Unregister a Mixed Handler */ removeMixer(RecentsMixedHandler mixer)123 public void removeMixer(RecentsMixedHandler mixer) { 124 mMixers.remove(mixer); 125 } 126 127 /** Adds the callback for receiving the state change of transition. */ addTransitionStateListener(RecentsTransitionStateListener listener)128 public void addTransitionStateListener(RecentsTransitionStateListener listener) { 129 mStateListeners.add(listener); 130 } 131 132 /** 133 * Sets a background color on the transition root layered behind the outgoing task. {@code null} 134 * may be used to clear any previously set colors to avoid showing a background at all. The 135 * color is always shown at full opacity. 136 */ setTransitionBackgroundColor(@ullable Color color)137 public void setTransitionBackgroundColor(@Nullable Color color) { 138 mBackgroundColor = color; 139 } 140 141 @VisibleForTesting startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener)142 public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, 143 IApplicationThread appThread, IRecentsAnimationRunner listener) { 144 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 145 "RecentsTransitionHandler.startRecentsTransition"); 146 147 // only care about latest one. 148 mAnimApp = appThread; 149 WindowContainerTransaction wct = new WindowContainerTransaction(); 150 wct.sendPendingIntent(intent, fillIn, options); 151 final RecentsController controller = new RecentsController(listener); 152 RecentsMixedHandler mixer = null; 153 Consumer<IBinder> setTransitionForMixer = null; 154 for (int i = 0; i < mMixers.size(); ++i) { 155 setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct); 156 if (setTransitionForMixer != null) { 157 mixer = mMixers.get(i); 158 break; 159 } 160 } 161 final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, 162 mixer == null ? this : mixer); 163 for (int i = 0; i < mStateListeners.size(); i++) { 164 mStateListeners.get(i).onTransitionStarted(transition); 165 } 166 if (mixer != null) { 167 setTransitionForMixer.accept(transition); 168 } 169 if (transition != null) { 170 controller.setTransition(transition); 171 mControllers.add(controller); 172 } else { 173 controller.cancel("startRecentsTransition"); 174 } 175 return transition; 176 } 177 178 @Override handleRequest(IBinder transition, TransitionRequestInfo request)179 public WindowContainerTransaction handleRequest(IBinder transition, 180 TransitionRequestInfo request) { 181 if (mControllers.isEmpty()) { 182 // Ignore if there is no running recents transition 183 return null; 184 } 185 final RecentsController controller = mControllers.get(mControllers.size() - 1); 186 controller.handleMidTransitionRequest(request); 187 return null; 188 } 189 findController(IBinder transition)190 private int findController(IBinder transition) { 191 for (int i = mControllers.size() - 1; i >= 0; --i) { 192 if (mControllers.get(i).mTransition == transition) return i; 193 } 194 return -1; 195 } 196 197 @Override startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback)198 public boolean startAnimation(IBinder transition, TransitionInfo info, 199 SurfaceControl.Transaction startTransaction, 200 SurfaceControl.Transaction finishTransaction, 201 Transitions.TransitionFinishCallback finishCallback) { 202 final int controllerIdx = findController(transition); 203 if (controllerIdx < 0) { 204 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 205 "RecentsTransitionHandler.startAnimation: no controller found"); 206 return false; 207 } 208 final RecentsController controller = mControllers.get(controllerIdx); 209 final IApplicationThread animApp = mAnimApp; 210 mAnimApp = null; 211 if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { 212 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 213 "RecentsTransitionHandler.startAnimation: failed to start animation"); 214 return false; 215 } 216 Transitions.setRunningRemoteTransitionDelegate(animApp); 217 return true; 218 } 219 220 @Override mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)221 public void mergeAnimation(IBinder transition, TransitionInfo info, 222 SurfaceControl.Transaction t, IBinder mergeTarget, 223 Transitions.TransitionFinishCallback finishCallback) { 224 final int targetIdx = findController(mergeTarget); 225 if (targetIdx < 0) { 226 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 227 "RecentsTransitionHandler.mergeAnimation: no controller found"); 228 return; 229 } 230 final RecentsController controller = mControllers.get(targetIdx); 231 controller.merge(info, t, finishCallback); 232 } 233 234 @Override onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction)235 public void onTransitionConsumed(IBinder transition, boolean aborted, 236 SurfaceControl.Transaction finishTransaction) { 237 // Only one recents transition can be handled at a time, but currently the first transition 238 // will trigger a no-op in the second transition which holds the active recents animation 239 // runner on the launcher side. For now, cancel all existing animations to ensure we 240 // don't get into a broken state with an orphaned animation runner, and later we can try to 241 // merge the latest transition into the currently running one 242 for (int i = mControllers.size() - 1; i >= 0; i--) { 243 mControllers.get(i).cancel("onTransitionConsumed"); 244 } 245 } 246 247 /** There is only one of these and it gets reset on finish. */ 248 private class RecentsController extends IRecentsAnimationController.Stub { 249 private final int mInstanceId; 250 251 private IRecentsAnimationRunner mListener; 252 private IBinder.DeathRecipient mDeathHandler; 253 private Transitions.TransitionFinishCallback mFinishCB = null; 254 private SurfaceControl.Transaction mFinishTransaction = null; 255 256 /** 257 * List of tasks that we are switching away from via this transition. Upon finish, these 258 * pausing tasks will become invisible. 259 * These need to be ordered since the order must be restored if there is no task-switch. 260 */ 261 private ArrayList<TaskState> mPausingTasks = null; 262 263 /** 264 * List of tasks were pausing but closed in a subsequent merged transition. If a 265 * closing task is reopened, the leash is not initially hidden since it is already 266 * visible. 267 */ 268 private ArrayList<TaskState> mClosingTasks = null; 269 270 /** 271 * List of tasks that we are switching to. Upon finish, these will remain visible and 272 * on top. 273 */ 274 private ArrayList<TaskState> mOpeningTasks = null; 275 276 private WindowContainerToken mPipTask = null; 277 private int mPipTaskId = -1; 278 private WindowContainerToken mRecentsTask = null; 279 private int mRecentsTaskId = -1; 280 private TransitionInfo mInfo = null; 281 private boolean mOpeningSeparateHome = false; 282 private boolean mPausingSeparateHome = false; 283 private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; 284 private PictureInPictureSurfaceTransaction mPipTransaction = null; 285 private IBinder mTransition = null; 286 private boolean mKeyguardLocked = false; 287 private boolean mWillFinishToHome = false; 288 private Transitions.TransitionHandler mTakeoverHandler = null; 289 290 /** The animation is idle, waiting for the user to choose a task to switch to. */ 291 private static final int STATE_NORMAL = 0; 292 293 /** The user chose a new task to switch to and the animation is animating to it. */ 294 private static final int STATE_NEW_TASK = 1; 295 296 /** The latest state that the recents animation is operating in. */ 297 private int mState = STATE_NORMAL; 298 299 // Snapshots taken when a new display change transition is requested, prior to the display 300 // change being applied. This pending set of snapshots will only be applied when cancel is 301 // next called. 302 private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel; 303 RecentsController(IRecentsAnimationRunner listener)304 RecentsController(IRecentsAnimationRunner listener) { 305 mInstanceId = System.identityHashCode(this); 306 mListener = listener; 307 mDeathHandler = () -> { 308 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 309 "[%d] RecentsController.DeathRecipient: binder died", mInstanceId); 310 finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */); 311 }; 312 try { 313 mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); 314 } catch (RemoteException e) { 315 Slog.e(TAG, "RecentsController: failed to link to death", e); 316 mListener = null; 317 } 318 } 319 setTransition(IBinder transition)320 void setTransition(IBinder transition) { 321 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 322 "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition); 323 mTransition = transition; 324 } 325 cancel(String reason)326 void cancel(String reason) { 327 // restoring (to-home = false) involves submitting more WM changes, so by default, use 328 // toHome = true when canceling. 329 cancel(true /* toHome */, false /* withScreenshots */, reason); 330 } 331 cancel(boolean toHome, boolean withScreenshots, String reason)332 void cancel(boolean toHome, boolean withScreenshots, String reason) { 333 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 334 "[%d] RecentsController.cancel: toHome=%b reason=%s", 335 mInstanceId, toHome, reason); 336 if (mListener != null) { 337 if (withScreenshots) { 338 sendCancelWithSnapshots(); 339 } else { 340 sendCancel(null, null); 341 } 342 } 343 if (mFinishCB != null) { 344 finishInner(toHome, false /* userLeave */, null /* finishCb */); 345 } else { 346 cleanUp(); 347 } 348 } 349 350 /** 351 * Sends a cancel message to the recents animation with snapshots. Used to trigger a 352 * "replace-with-screenshot" like behavior. 353 */ sendCancelWithSnapshots()354 private boolean sendCancelWithSnapshots() { 355 Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null 356 ? mPendingPauseSnapshotsForCancel 357 : getSnapshotsForPausingTasks(); 358 return sendCancel(snapshots.first, snapshots.second); 359 } 360 361 /** 362 * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot. 363 */ getSnapshotsForPausingTasks()364 private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() { 365 int[] taskIds = null; 366 TaskSnapshot[] snapshots = null; 367 if (mPausingTasks != null && mPausingTasks.size() > 0) { 368 taskIds = new int[mPausingTasks.size()]; 369 snapshots = new TaskSnapshot[mPausingTasks.size()]; 370 try { 371 for (int i = 0; i < mPausingTasks.size(); ++i) { 372 TaskState state = mPausingTasks.get(0); 373 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 374 "[%d] RecentsController.sendCancel: Snapshotting task=%d", 375 mInstanceId, state.mTaskInfo.taskId); 376 snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot( 377 state.mTaskInfo.taskId, true /* updateCache */); 378 } 379 } catch (RemoteException e) { 380 taskIds = null; 381 snapshots = null; 382 } 383 } 384 return new Pair(taskIds, snapshots); 385 } 386 387 /** 388 * Sends a cancel message to the recents animation. 389 */ sendCancel(@ullable int[] taskIds, @Nullable TaskSnapshot[] taskSnapshots)390 private boolean sendCancel(@Nullable int[] taskIds, 391 @Nullable TaskSnapshot[] taskSnapshots) { 392 try { 393 final String cancelDetails = taskSnapshots != null ? "with snapshots" : ""; 394 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 395 "[%d] RecentsController.cancel: calling onAnimationCanceled %s", 396 mInstanceId, cancelDetails); 397 mListener.onAnimationCanceled(taskIds, taskSnapshots); 398 return true; 399 } catch (RemoteException e) { 400 Slog.e(TAG, "Error canceling recents animation", e); 401 return false; 402 } 403 } 404 405 /** 406 * Cleans up the recents transition. This should generally not be called directly 407 * to cancel a transition after it has started, instead callers should call one of 408 * the cancel() methods to ensure that Launcher is notified. 409 */ cleanUp()410 void cleanUp() { 411 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 412 "[%d] RecentsController.cleanup", mInstanceId); 413 if (mListener != null && mDeathHandler != null) { 414 mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */); 415 mDeathHandler = null; 416 } 417 mListener = null; 418 mFinishCB = null; 419 // clean-up leash surfacecontrols and anything that might reference them. 420 if (mLeashMap != null) { 421 for (int i = 0; i < mLeashMap.size(); ++i) { 422 mLeashMap.valueAt(i).release(); 423 } 424 mLeashMap = null; 425 } 426 mFinishTransaction = null; 427 mPausingTasks = null; 428 mClosingTasks = null; 429 mOpeningTasks = null; 430 mInfo = null; 431 mTransition = null; 432 mPendingPauseSnapshotsForCancel = null; 433 mControllers.remove(this); 434 for (int i = 0; i < mStateListeners.size(); i++) { 435 mStateListeners.get(i).onAnimationStateChanged(false); 436 } 437 } 438 start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB)439 boolean start(TransitionInfo info, SurfaceControl.Transaction t, 440 SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { 441 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 442 "[%d] RecentsController.start", mInstanceId); 443 if (mListener == null || mTransition == null) { 444 Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) + 445 " hasTransition=" + (mTransition != null)); 446 cancel("No listener (" + (mListener == null) 447 + ") or no transition (" + (mTransition == null) + ")"); 448 return false; 449 } 450 // First see if this is a valid recents transition. 451 boolean hasPausingTasks = false; 452 for (int i = 0; i < info.getChanges().size(); ++i) { 453 final TransitionInfo.Change change = info.getChanges().get(i); 454 if (TransitionUtil.isWallpaper(change)) continue; 455 if (TransitionUtil.isClosingType(change.getMode())) { 456 hasPausingTasks = true; 457 continue; 458 } 459 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 460 if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { 461 mRecentsTask = taskInfo.token; 462 mRecentsTaskId = taskInfo.taskId; 463 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 464 mRecentsTask = taskInfo.token; 465 mRecentsTaskId = taskInfo.taskId; 466 } 467 } 468 if (mRecentsTask == null && !hasPausingTasks) { 469 // Recents is already running apparently, so this is a no-op. 470 Slog.e(TAG, "Tried to start recents while it is already running."); 471 cancel("No recents task and no pausing tasks"); 472 return false; 473 } 474 475 mInfo = info; 476 mFinishCB = finishCB; 477 mFinishTransaction = finishT; 478 mPausingTasks = new ArrayList<>(); 479 mClosingTasks = new ArrayList<>(); 480 mOpeningTasks = new ArrayList<>(); 481 mLeashMap = new ArrayMap<>(); 482 mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; 483 484 int closingSplitTaskId = INVALID_TASK_ID; 485 final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); 486 final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>(); 487 TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter(); 488 // About layering: we divide up the "layer space" into 3 regions (each the size of 489 // the change count). This lets us categorize things into above/below/between 490 // while maintaining their relative ordering. 491 final int belowLayers = info.getChanges().size(); 492 final int middleLayers = info.getChanges().size() * 2; 493 final int aboveLayers = info.getChanges().size() * 3; 494 495 // Add a background color to each transition root in this transition. 496 if (mBackgroundColor != null) { 497 info.getChanges().stream() 498 .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info)) 499 .distinct() 500 .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash()) 501 .forEach((root) -> createBackgroundSurface(t, root, middleLayers)); 502 } 503 504 for (int i = 0; i < info.getChanges().size(); ++i) { 505 final TransitionInfo.Change change = info.getChanges().get(i); 506 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 507 if (TransitionUtil.isWallpaper(change)) { 508 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 509 // wallpapers go into the "below" layer space 510 belowLayers - i, info, t, mLeashMap); 511 wallpapers.add(target); 512 // Make all the wallpapers opaque since we want them visible from the start 513 t.setAlpha(target.leash, 1); 514 } else if (leafTaskFilter.test(change)) { 515 // start by putting everything into the "below" layer space. 516 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 517 belowLayers - i, info, t, mLeashMap); 518 apps.add(target); 519 if (TransitionUtil.isClosingType(change.getMode())) { 520 mPausingTasks.add(new TaskState(change, target.leash)); 521 closingSplitTaskId = change.getTaskInfo().taskId; 522 if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 523 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 524 " adding pausing leaf home taskId=%d", taskInfo.taskId); 525 // This can only happen if we have a separate recents/home (3p launcher) 526 mPausingSeparateHome = true; 527 } else { 528 final int layer = aboveLayers - i; 529 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 530 " adding pausing leaf taskId=%d at layer=%d", 531 taskInfo.taskId, layer); 532 // raise closing (pausing) task to "above" layer so it isn't covered 533 t.setLayer(target.leash, layer); 534 } 535 if (taskInfo.pictureInPictureParams != null 536 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { 537 mPipTask = taskInfo.token; 538 } 539 } else if (taskInfo != null 540 && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { 541 final int layer = middleLayers - i; 542 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 543 " setting recents activity layer=%d", layer); 544 // There's a 3p launcher, so make sure recents goes above that, but under 545 // the pausing apps. 546 t.setLayer(target.leash, layer); 547 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 548 // do nothing 549 } else if (TransitionUtil.isOpeningType(change.getMode())) { 550 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 551 " adding opening leaf taskId=%d", taskInfo.taskId); 552 mOpeningTasks.add(new TaskState(change, target.leash)); 553 } 554 } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) { 555 // Root tasks 556 if (TransitionUtil.isClosingType(change.getMode())) { 557 final int layer = aboveLayers - i; 558 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 559 " adding pausing taskId=%d at layer=%d", taskInfo.taskId, layer); 560 // raise closing (pausing) task to "above" layer so it isn't covered 561 t.setLayer(change.getLeash(), layer); 562 mPausingTasks.add(new TaskState(change, null /* leash */)); 563 } else if (TransitionUtil.isOpeningType(change.getMode())) { 564 final int layer = belowLayers - i; 565 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 566 " adding opening taskId=%d at layer=%d", taskInfo.taskId, layer); 567 // Put into the "below" layer space. 568 t.setLayer(change.getLeash(), layer); 569 mOpeningTasks.add(new TaskState(change, null /* leash */)); 570 } else { 571 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 572 " unhandled root taskId=%d", taskInfo.taskId); 573 } 574 } else if (TransitionUtil.isDividerBar(change)) { 575 final RemoteAnimationTarget target = TransitionUtil.newTarget(change, 576 belowLayers - i, info, t, mLeashMap); 577 // Add this as a app and we will separate them on launcher side by window type. 578 apps.add(target); 579 } else { 580 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 581 " unhandled change taskId=%d", 582 taskInfo != null ? taskInfo.taskId : -1); 583 } 584 } 585 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 586 "Applying transaction=%d", t.getId()); 587 t.apply(); 588 589 mTakeoverHandler = mTransitions.getHandlerForTakeover(mTransition, info); 590 591 Bundle b = new Bundle(2 /*capacity*/); 592 b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, 593 mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId)); 594 b.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, mTakeoverHandler != null); 595 try { 596 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 597 "[%d] RecentsController.start: calling onAnimationStart with %d apps", 598 mInstanceId, apps.size()); 599 mListener.onAnimationStart(this, 600 apps.toArray(new RemoteAnimationTarget[apps.size()]), 601 wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), 602 new Rect(0, 0, 0, 0), new Rect(), b); 603 for (int i = 0; i < mStateListeners.size(); i++) { 604 mStateListeners.get(i).onAnimationStateChanged(true); 605 } 606 } catch (RemoteException e) { 607 Slog.e(TAG, "Error starting recents animation", e); 608 cancel("onAnimationStart() failed"); 609 } 610 return true; 611 } 612 613 @Override handOffAnimation( RemoteAnimationTarget[] targets, WindowAnimationState[] states)614 public void handOffAnimation( 615 RemoteAnimationTarget[] targets, WindowAnimationState[] states) { 616 mExecutor.execute(() -> { 617 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 618 "[%d] RecentsController.handOffAnimation", mInstanceId); 619 620 if (mTakeoverHandler == null) { 621 Slog.e(TAG, "Tried to hand off an animation without a valid takeover " 622 + "handler."); 623 return; 624 } 625 626 if (targets.length != states.length) { 627 Slog.e(TAG, "Tried to hand off an animation, but the number of targets " 628 + "(" + targets.length + ") doesn't match the number of states " 629 + "(" + states.length + ")"); 630 return; 631 } 632 633 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 634 "[%d] RecentsController.handOffAnimation: got %d states for %d " 635 + "changes", mInstanceId, states.length, mInfo.getChanges().size()); 636 WindowAnimationState[] updatedStates = 637 new WindowAnimationState[mInfo.getChanges().size()]; 638 639 // Ensure that the ordering of animation states is the same as that of matching 640 // changes in mInfo. prefixOrderIndex is set up in reverse order to that of the 641 // changes, so that's what we use to get to the correct ordering. 642 for (int i = 0; i < targets.length; i++) { 643 RemoteAnimationTarget target = targets[i]; 644 updatedStates[updatedStates.length - target.prefixOrderIndex] = states[i]; 645 } 646 647 Transitions.TransitionFinishCallback finishCB = mFinishCB; 648 // Reset the callback here, so any stray calls that aren't coming from the new 649 // handler are ignored. 650 mFinishCB = null; 651 652 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 653 "[%d] RecentsController.handOffAnimation: calling " 654 + "takeOverAnimation with %d states", mInstanceId, 655 updatedStates.length); 656 mTakeoverHandler.takeOverAnimation( 657 mTransition, mInfo, new SurfaceControl.Transaction(), 658 wct -> { 659 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 660 "[%d] RecentsController.handOffAnimation: finish " 661 + "callback", mInstanceId); 662 // Set the callback once again so we can finish correctly. 663 mFinishCB = finishCB; 664 finishInner(true /* toHome */, false /* userLeave */, 665 null /* finishCb */); 666 }, updatedStates); 667 }); 668 } 669 670 /** 671 * Updates this controller when a new transition is requested mid-recents transition. 672 */ handleMidTransitionRequest(TransitionRequestInfo request)673 void handleMidTransitionRequest(TransitionRequestInfo request) { 674 if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) { 675 final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange(); 676 if (dispChange.getStartRotation() != dispChange.getEndRotation()) { 677 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 678 "[%d] RecentsController.prepareForMerge: " 679 + "Snapshot due to requested display change", 680 mInstanceId); 681 mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks(); 682 } 683 } 684 } 685 686 @SuppressLint("NewApi") merge(TransitionInfo info, SurfaceControl.Transaction t, Transitions.TransitionFinishCallback finishCallback)687 void merge(TransitionInfo info, SurfaceControl.Transaction t, 688 Transitions.TransitionFinishCallback finishCallback) { 689 if (mFinishCB == null) { 690 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 691 "[%d] RecentsController.merge: skip, no finish callback", 692 mInstanceId); 693 // This was no-op'd (likely a repeated start) and we've already sent finish. 694 return; 695 } 696 if (info.getType() == TRANSIT_SLEEP) { 697 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 698 "[%d] RecentsController.merge: transit_sleep", mInstanceId); 699 // A sleep event means we need to stop animations immediately, so cancel here. 700 cancel("transit_sleep"); 701 return; 702 } 703 if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { 704 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 705 "[%d] RecentsController.merge: keyguard is locked", mInstanceId); 706 // We will not accept new changes if we are swiping over the keyguard. 707 cancel(true /* toHome */, false /* withScreenshots */, "keyguard_locked"); 708 return; 709 } 710 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 711 "[%d] RecentsController.merge", mInstanceId); 712 // Keep all tasks in one list because order matters. 713 ArrayList<TransitionInfo.Change> openingTasks = null; 714 IntArray openingTaskIsLeafs = null; 715 ArrayList<TransitionInfo.Change> closingTasks = null; 716 mOpeningSeparateHome = false; 717 TransitionInfo.Change recentsOpening = null; 718 boolean foundRecentsClosing = false; 719 boolean hasChangingApp = false; 720 final TransitionUtil.LeafTaskFilter leafTaskFilter = 721 new TransitionUtil.LeafTaskFilter(); 722 boolean hasTaskChange = false; 723 for (int i = 0; i < info.getChanges().size(); ++i) { 724 final TransitionInfo.Change change = info.getChanges().get(i); 725 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 726 if (taskInfo != null 727 && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { 728 // Tasks that are always on top (e.g. bubbles), will handle their own transition 729 // as they are on top of everything else. So cancel the merge here. 730 cancel(false /* toHome */, false /* withScreenshots */, 731 "task #" + taskInfo.taskId + " is always_on_top"); 732 return; 733 } 734 final boolean isRootTask = taskInfo != null 735 && TransitionInfo.isIndependent(change, info); 736 final boolean isRecentsTask = mRecentsTask != null 737 && mRecentsTask.equals(change.getContainer()); 738 hasTaskChange = hasTaskChange || isRootTask; 739 final boolean isLeafTask = leafTaskFilter.test(change); 740 if (TransitionUtil.isOpeningType(change.getMode()) 741 || TransitionUtil.isOrderOnly(change)) { 742 if (isRecentsTask) { 743 recentsOpening = change; 744 } else if (isRootTask || isLeafTask) { 745 if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 746 // This is usually a 3p launcher 747 mOpeningSeparateHome = true; 748 } 749 if (openingTasks == null) { 750 openingTasks = new ArrayList<>(); 751 openingTaskIsLeafs = new IntArray(); 752 } 753 openingTasks.add(change); 754 openingTaskIsLeafs.add(isLeafTask ? 1 : 0); 755 } 756 } else if (TransitionUtil.isClosingType(change.getMode())) { 757 if (isRecentsTask) { 758 foundRecentsClosing = true; 759 } else if (isRootTask || isLeafTask) { 760 if (closingTasks == null) { 761 closingTasks = new ArrayList<>(); 762 } 763 closingTasks.add(change); 764 } 765 } else if (change.getMode() == TRANSIT_CHANGE) { 766 // Finish recents animation if the display is changed, so the default 767 // transition handler can play the animation such as rotation effect. 768 if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) 769 && info.getType() == TRANSIT_CHANGE) { 770 // This call to cancel will use the screenshots taken preemptively in 771 // handleMidTransitionRequest() prior to the display changing 772 cancel(mWillFinishToHome, true /* withScreenshots */, "display change"); 773 return; 774 } 775 // Don't consider order-only & non-leaf changes as changing apps. 776 if (!TransitionUtil.isOrderOnly(change) && isLeafTask) { 777 hasChangingApp = true; 778 } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME 779 && !isRecentsTask ) { 780 // Unless it is a 3p launcher. This means that the 3p launcher was already 781 // visible (eg. the "pausing" task is translucent over the 3p launcher). 782 // Treat it as if we are "re-opening" the 3p launcher. 783 if (openingTasks == null) { 784 openingTasks = new ArrayList<>(); 785 openingTaskIsLeafs = new IntArray(); 786 } 787 openingTasks.add(change); 788 openingTaskIsLeafs.add(1); 789 } 790 } 791 } 792 if (hasChangingApp && foundRecentsClosing) { 793 // This happens when a visible app is expanding (usually PiP). In this case, 794 // that transition probably has a special-purpose animation, so finish recents 795 // now and let it do its animation (since recents is going to be occluded). 796 sendCancelWithSnapshots(); 797 mExecutor.executeDelayed( 798 () -> finishInner(true /* toHome */, false /* userLeaveHint */, 799 null /* finishCb */), 0); 800 return; 801 } 802 if (recentsOpening != null) { 803 // the recents task re-appeared. This happens if the user gestures before the 804 // task-switch (NEW_TASK) animation finishes. 805 if (mState == STATE_NORMAL) { 806 Slog.e(TAG, "Returning to recents while recents is already idle."); 807 } 808 if (closingTasks == null || closingTasks.size() == 0) { 809 Slog.e(TAG, "Returning to recents without closing any opening tasks."); 810 } 811 // Setup may hide it initially since it doesn't know that overview was still active. 812 t.show(recentsOpening.getLeash()); 813 t.setAlpha(recentsOpening.getLeash(), 1.f); 814 mState = STATE_NORMAL; 815 } 816 boolean didMergeThings = false; 817 if (closingTasks != null) { 818 // Potentially cancelling a task-switch. Move the tasks back to mPausing if they 819 // are in mOpening. 820 for (int i = 0; i < closingTasks.size(); ++i) { 821 final TransitionInfo.Change change = closingTasks.get(i); 822 final int pausingIdx = TaskState.indexOf(mPausingTasks, change); 823 if (pausingIdx >= 0) { 824 // We are closing the pausing task, but it is still visible and can be 825 // restart by another transition prior to this transition finishing 826 final TaskState closingTask = mPausingTasks.remove(pausingIdx); 827 mClosingTasks.add(closingTask); 828 didMergeThings = true; 829 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 830 " closing pausing taskId=%d", change.getTaskInfo().taskId); 831 continue; 832 } 833 int openingIdx = TaskState.indexOf(mOpeningTasks, change); 834 if (openingIdx < 0) { 835 Slog.w(TAG, "Closing a task that wasn't opening, this may be split or" 836 + " something unexpected: " + change.getTaskInfo().taskId); 837 continue; 838 } 839 final TaskState openingTask = mOpeningTasks.remove(openingIdx); 840 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 841 " pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "", 842 openingTask.mTaskInfo.taskId); 843 mPausingTasks.add(openingTask); 844 didMergeThings = true; 845 } 846 } 847 RemoteAnimationTarget[] appearedTargets = null; 848 if (openingTasks != null && openingTasks.size() > 0) { 849 // Switching to some new tasks, add to mOpening and remove from mPausing. Also, 850 // enter NEW_TASK state since this will start the switch-to animation. 851 final int layer = mInfo.getChanges().size() * 3; 852 int openingLeafCount = 0; 853 for (int i = 0; i < openingTaskIsLeafs.size(); ++i) { 854 openingLeafCount += openingTaskIsLeafs.get(i); 855 } 856 if (openingLeafCount > 0) { 857 appearedTargets = new RemoteAnimationTarget[openingLeafCount]; 858 } 859 int nextTargetIdx = 0; 860 for (int i = 0; i < openingTasks.size(); ++i) { 861 final TransitionInfo.Change change = openingTasks.get(i); 862 final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; 863 final int closingIdx = TaskState.indexOf(mClosingTasks, change); 864 if (closingIdx >= 0) { 865 // Remove opening tasks from closing set 866 mClosingTasks.remove(closingIdx); 867 } 868 final int pausingIdx = TaskState.indexOf(mPausingTasks, change); 869 if (pausingIdx >= 0) { 870 // Something is showing/opening a previously-pausing app. 871 if (isLeaf) { 872 appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget( 873 change, layer, mPausingTasks.get(pausingIdx).mLeash); 874 } 875 final TaskState pausingTask = mPausingTasks.remove(pausingIdx); 876 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 877 " opening pausing %staskId=%d", isLeaf ? "leaf " : "", 878 pausingTask.mTaskInfo.taskId); 879 mOpeningTasks.add(pausingTask); 880 // Setup hides opening tasks initially, so make it visible again (since we 881 // are already showing it). 882 t.show(change.getLeash()); 883 t.setAlpha(change.getLeash(), 1.f); 884 } else if (isLeaf) { 885 // We are receiving new opening leaf tasks, so convert to onTasksAppeared. 886 final RemoteAnimationTarget target = TransitionUtil.newTarget( 887 change, layer, info, t, mLeashMap); 888 appearedTargets[nextTargetIdx++] = target; 889 // reparent into the original `mInfo` since that's where we are animating. 890 final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); 891 final boolean wasClosing = closingIdx >= 0; 892 t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); 893 t.setLayer(target.leash, layer); 894 if (wasClosing) { 895 // App was previously visible and is closing 896 t.show(target.leash); 897 t.setAlpha(target.leash, 1f); 898 // Also override the task alpha as it was set earlier when dispatching 899 // the transition and setting up the leash to hide the 900 t.setAlpha(change.getLeash(), 1f); 901 } else { 902 // Hide the animation leash, let the listener show it 903 t.hide(target.leash); 904 } 905 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 906 " opening new leaf taskId=%d wasClosing=%b", 907 target.taskId, wasClosing); 908 mOpeningTasks.add(new TaskState(change, target.leash)); 909 } else { 910 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 911 " opening new taskId=%d", change.getTaskInfo().taskId); 912 t.setLayer(change.getLeash(), layer); 913 // Setup hides opening tasks initially, so make it visible since recents 914 // is only animating the leafs. 915 t.show(change.getLeash()); 916 mOpeningTasks.add(new TaskState(change, null)); 917 } 918 } 919 didMergeThings = true; 920 mState = STATE_NEW_TASK; 921 } 922 if (mPausingTasks.isEmpty()) { 923 // The pausing tasks may be removed by the incoming closing tasks. 924 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 925 "[%d] RecentsController.merge: empty pausing tasks", mInstanceId); 926 } 927 if (!hasTaskChange) { 928 // Activity only transition, so consume the merge as it doesn't affect the rest of 929 // recents. 930 Slog.d(TAG, "Got an activity only transition during recents, so apply directly"); 931 mergeActivityOnly(info, t); 932 } else if (!didMergeThings) { 933 // Didn't recognize anything in incoming transition so don't merge it. 934 Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing=" 935 + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId); 936 if (foundRecentsClosing || mRecentsTaskId < 0) { 937 mWillFinishToHome = false; 938 cancel(false /* toHome */, false /* withScreenshots */, "didn't merge"); 939 } 940 return; 941 } 942 // At this point, we are accepting the merge. 943 t.apply(); 944 // not using the incoming anim-only surfaces 945 info.releaseAnimSurfaces(); 946 if (appearedTargets != null) { 947 try { 948 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 949 "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId); 950 mListener.onTasksAppeared(appearedTargets); 951 } catch (RemoteException e) { 952 Slog.e(TAG, "Error sending appeared tasks to recents animation", e); 953 } 954 } 955 finishCallback.onTransitionFinished(null /* wct */); 956 } 957 958 /** For now, just set-up a jump-cut to the new activity. */ mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t)959 private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) { 960 for (int i = 0; i < info.getChanges().size(); ++i) { 961 final TransitionInfo.Change change = info.getChanges().get(i); 962 if (TransitionUtil.isOpeningType(change.getMode())) { 963 t.show(change.getLeash()); 964 t.setAlpha(change.getLeash(), 1.f); 965 } else if (TransitionUtil.isClosingType(change.getMode())) { 966 t.hide(change.getLeash()); 967 } 968 } 969 } 970 971 @Override screenshotTask(int taskId)972 public TaskSnapshot screenshotTask(int taskId) { 973 try { 974 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 975 "[%d] RecentsController.screenshotTask: taskId=%d", mInstanceId, taskId); 976 return ActivityTaskManager.getService().takeTaskSnapshot(taskId, 977 true /* updateCache */); 978 } catch (RemoteException e) { 979 Slog.e(TAG, "Failed to screenshot task", e); 980 } 981 return null; 982 } 983 984 @Override setInputConsumerEnabled(boolean enabled)985 public void setInputConsumerEnabled(boolean enabled) { 986 mExecutor.execute(() -> { 987 if (mFinishCB == null || !enabled) { 988 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 989 "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b", 990 mFinishCB != null, enabled); 991 return; 992 } 993 final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId() 994 : Display.DEFAULT_DISPLAY; 995 // transient launches don't receive focus automatically. Since we are taking over 996 // the gesture now, take focus explicitly. 997 // This also moves recents back to top if the user gestured before a switch 998 // animation finished. 999 try { 1000 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1001 "[%d] RecentsController.setInputConsumerEnabled: set focus to recents", 1002 mInstanceId); 1003 ActivityTaskManager.getService().focusTopTask(displayId); 1004 } catch (RemoteException e) { 1005 Slog.e(TAG, "Failed to set focused task", e); 1006 } 1007 }); 1008 } 1009 1010 @Override setAnimationTargetsBehindSystemBars(boolean behindSystemBars)1011 public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { 1012 } 1013 1014 @Override setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)1015 public void setFinishTaskTransaction(int taskId, 1016 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { 1017 mExecutor.execute(() -> { 1018 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1019 "[%d] RecentsController.setFinishTaskTransaction: taskId=%d," 1020 + " [mFinishCB is non-null]=%b", 1021 mInstanceId, taskId, mFinishCB != null); 1022 if (mFinishCB == null) return; 1023 mPipTransaction = finishTransaction; 1024 mPipTaskId = taskId; 1025 }); 1026 } 1027 1028 @Override 1029 @SuppressLint("NewApi") finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb)1030 public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) { 1031 mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb)); 1032 } 1033 finishInner(boolean toHome, boolean sendUserLeaveHint, IResultReceiver runnerFinishCb)1034 private void finishInner(boolean toHome, boolean sendUserLeaveHint, 1035 IResultReceiver runnerFinishCb) { 1036 if (mFinishCB == null) { 1037 Slog.e(TAG, "Duplicate call to finish"); 1038 return; 1039 } 1040 1041 boolean returningToApp = !toHome 1042 && !mWillFinishToHome 1043 && mPausingTasks != null 1044 && mState == STATE_NORMAL; 1045 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { 1046 mHomeTransitionObserver.notifyHomeVisibilityChanged(true); 1047 } else if (!toHome) { 1048 // For some transitions, we may have notified home activity that it became visible. 1049 // We need to notify the observer that we are no longer going home. 1050 mHomeTransitionObserver.notifyHomeVisibilityChanged(false); 1051 } 1052 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1053 "[%d] RecentsController.finishInner: toHome=%b userLeave=%b " 1054 + "willFinishToHome=%b state=%d", 1055 mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState); 1056 final Transitions.TransitionFinishCallback finishCB = mFinishCB; 1057 mFinishCB = null; 1058 1059 final SurfaceControl.Transaction t = mFinishTransaction; 1060 final WindowContainerTransaction wct = new WindowContainerTransaction(); 1061 1062 if (mKeyguardLocked && mRecentsTask != null) { 1063 if (toHome) wct.reorder(mRecentsTask, true /* toTop */); 1064 else wct.restoreTransientOrder(mRecentsTask); 1065 } 1066 if (returningToApp) { 1067 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); 1068 // The gesture is returning to the pausing-task(s) rather than continuing with 1069 // recents, so end the transition by moving the app back to the top (and also 1070 // re-showing it's task). 1071 for (int i = mPausingTasks.size() - 1; i >= 0; --i) { 1072 // reverse order so that index 0 ends up on top 1073 wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); 1074 t.show(mPausingTasks.get(i).mTaskSurface); 1075 } 1076 if (!mKeyguardLocked && mRecentsTask != null) { 1077 wct.restoreTransientOrder(mRecentsTask); 1078 } 1079 } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { 1080 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " 3p launching home"); 1081 // Special situation where 3p launcher was changed during recents (this happens 1082 // during tapltests...). Here we get both "return to home" AND "home opening". 1083 // This is basically going home, but we have to restore the recents and home order. 1084 for (int i = 0; i < mOpeningTasks.size(); ++i) { 1085 final TaskState state = mOpeningTasks.get(i); 1086 if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) { 1087 // Make sure it is on top. 1088 wct.reorder(state.mToken, true /* onTop */); 1089 } 1090 t.show(state.mTaskSurface); 1091 } 1092 for (int i = mPausingTasks.size() - 1; i >= 0; --i) { 1093 t.hide(mPausingTasks.get(i).mTaskSurface); 1094 } 1095 if (!mKeyguardLocked && mRecentsTask != null) { 1096 wct.restoreTransientOrder(mRecentsTask); 1097 } 1098 } else { 1099 if (mPausingSeparateHome) { 1100 if (mOpeningTasks.isEmpty()) { 1101 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1102 " recents occluded 3p home"); 1103 } else { 1104 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1105 " switch task by recents on 3p home"); 1106 } 1107 } 1108 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); 1109 // The general case: committing to recents, going home, or switching tasks. 1110 for (int i = 0; i < mOpeningTasks.size(); ++i) { 1111 t.show(mOpeningTasks.get(i).mTaskSurface); 1112 } 1113 for (int i = 0; i < mPausingTasks.size(); ++i) { 1114 cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint); 1115 } 1116 for (int i = 0; i < mClosingTasks.size(); ++i) { 1117 cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint); 1118 } 1119 if (mPipTransaction != null && sendUserLeaveHint) { 1120 SurfaceControl pipLeash = null; 1121 TransitionInfo.Change pipChange = null; 1122 if (mPipTask != null) { 1123 pipChange = mInfo.getChange(mPipTask); 1124 pipLeash = pipChange.getLeash(); 1125 } else if (mPipTaskId != -1) { 1126 // find a task with taskId from #setFinishTaskTransaction() 1127 for (TransitionInfo.Change change : mInfo.getChanges()) { 1128 if (change.getTaskInfo() != null 1129 && change.getTaskInfo().taskId == mPipTaskId) { 1130 pipChange = change; 1131 pipLeash = change.getLeash(); 1132 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1133 "RecentsController.finishInner:" 1134 + " found a change with taskId=%d", mPipTaskId); 1135 } 1136 } 1137 } 1138 if (pipLeash == null) { 1139 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1140 "RecentsController.finishInner: no valid PiP leash;" 1141 + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d", 1142 mPipTransaction, mPipTask, mPipTaskId); 1143 } else { 1144 t.show(pipLeash); 1145 PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t); 1146 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1147 "RecentsController.finishInner: PiP transaction %s merged", 1148 mPipTransaction); 1149 if (PipUtils.isPip2ExperimentEnabled()) { 1150 // If this path is triggered, we are in auto-enter PiP flow in gesture 1151 // navigation mode, which means "Recents" transition should be followed 1152 // by a TRANSIT_PIP. Hence, we take the WCT was about to be sent 1153 // to Core to be applied during finishTransition(), we modify it to 1154 // factor in PiP changes, and we send it as a direct startWCT for 1155 // a new TRANSIT_PIP type transition. Recents still sends 1156 // finishTransition() to update visibilities, but with finishWCT=null. 1157 TransitionRequestInfo requestInfo = new TransitionRequestInfo( 1158 TRANSIT_PIP, null /* triggerTask */, pipChange.getTaskInfo(), 1159 null /* remote */, null /* displayChange */, 0 /* flags */); 1160 // Use mTransition IBinder token temporarily just to get PipTransition 1161 // to return from its handleRequest(). The actual TRANSIT_PIP will have 1162 // anew token once it arrives into PipTransition#startAnimation(). 1163 Pair<Transitions.TransitionHandler, WindowContainerTransaction> 1164 requestRes = mTransitions.dispatchRequest(mTransition, 1165 requestInfo, null /* skip */); 1166 wct.merge(requestRes.second, true); 1167 mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */); 1168 // We need to clear the WCT to send finishWCT=null for Recents. 1169 wct.clear(); 1170 } 1171 } 1172 mPipTaskId = -1; 1173 mPipTask = null; 1174 mPipTransaction = null; 1175 } 1176 } 1177 cleanUp(); 1178 finishCB.onTransitionFinished(wct.isEmpty() ? null : wct); 1179 if (runnerFinishCb != null) { 1180 try { 1181 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1182 "[%d] RecentsController.finishInner: calling finish callback", 1183 mInstanceId); 1184 runnerFinishCb.send(0, null); 1185 } catch (RemoteException e) { 1186 Slog.e(TAG, "Failed to report transition finished", e); 1187 } 1188 } 1189 } 1190 allAppsAreTranslucent(ArrayList<TaskState> tasks)1191 private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) { 1192 if (tasks == null) { 1193 return false; 1194 } 1195 for (int i = tasks.size() - 1; i >= 0; --i) { 1196 if (!tasks.get(i).mIsTranslucent) { 1197 return false; 1198 } 1199 } 1200 return true; 1201 } 1202 createBackgroundSurface(SurfaceControl.Transaction transaction, SurfaceControl parent, int layer)1203 private void createBackgroundSurface(SurfaceControl.Transaction transaction, 1204 SurfaceControl parent, int layer) { 1205 if (mBackgroundColor == null) { 1206 return; 1207 } 1208 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1209 " adding background color to layer=%d", layer); 1210 final SurfaceControl background = new SurfaceControl.Builder() 1211 .setName("recents_background") 1212 .setColorLayer() 1213 .setOpaque(true) 1214 .setParent(parent) 1215 .build(); 1216 transaction.setColor(background, colorToFloatArray(mBackgroundColor)); 1217 transaction.setLayer(background, layer); 1218 transaction.setAlpha(background, 1F); 1219 transaction.show(background); 1220 } 1221 colorToFloatArray(@onNull Color color)1222 private static float[] colorToFloatArray(@NonNull Color color) { 1223 return new float[]{color.red(), color.green(), color.blue()}; 1224 } 1225 cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint)1226 private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, 1227 SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { 1228 if (!sendUserLeaveHint && task.isLeaf()) { 1229 // This means recents is not *actually* finishing, so of course we gotta 1230 // do special stuff in WMCore to accommodate. 1231 wct.setDoNotPip(task.mToken); 1232 } 1233 // Since we will reparent out of the leashes, pre-emptively hide the child 1234 // surface to match the leash. Otherwise, there will be a flicker before the 1235 // visibility gets committed in Core when using split-screen (in splitscreen, 1236 // the leaf-tasks are not "independent" so aren't hidden by normal setup). 1237 finishTransaction.hide(task.mTaskSurface); 1238 } 1239 1240 @Override setDeferCancelUntilNextTransition(boolean defer, boolean screenshot)1241 public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { 1242 } 1243 1244 @Override cleanupScreenshot()1245 public void cleanupScreenshot() { 1246 } 1247 1248 @Override setWillFinishToHome(boolean willFinishToHome)1249 public void setWillFinishToHome(boolean willFinishToHome) { 1250 mExecutor.execute(() -> { 1251 mWillFinishToHome = willFinishToHome; 1252 }); 1253 } 1254 1255 /** 1256 * @see IRecentsAnimationController#removeTask 1257 */ 1258 @Override removeTask(int taskId)1259 public boolean removeTask(int taskId) { 1260 return false; 1261 } 1262 1263 /** 1264 * @see IRecentsAnimationController#detachNavigationBarFromApp 1265 */ 1266 @Override detachNavigationBarFromApp(boolean moveHomeToTop)1267 public void detachNavigationBarFromApp(boolean moveHomeToTop) { 1268 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 1269 "[%d] RecentsController.detachNavigationBarFromApp", mInstanceId); 1270 mExecutor.execute(() -> { 1271 if (mTransition == null) return; 1272 try { 1273 ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition); 1274 } catch (RemoteException e) { 1275 Slog.e(TAG, "Failed to detach the navigation bar from app", e); 1276 } 1277 }); 1278 } 1279 1280 /** 1281 * @see IRecentsAnimationController#animateNavigationBarToApp(long) 1282 */ 1283 @Override animateNavigationBarToApp(long duration)1284 public void animateNavigationBarToApp(long duration) { 1285 } 1286 }; 1287 1288 /** Utility class to track the state of a task as-seen by recents. */ 1289 private static class TaskState { 1290 WindowContainerToken mToken; 1291 ActivityManager.RunningTaskInfo mTaskInfo; 1292 1293 /** The surface/leash of the task provided by Core. */ 1294 SurfaceControl mTaskSurface; 1295 1296 /** True when the task is translucent. */ 1297 final boolean mIsTranslucent; 1298 1299 /** The (local) animation-leash created for this task. Only non-null for leafs. */ 1300 @Nullable 1301 SurfaceControl mLeash; 1302 TaskState(TransitionInfo.Change change, SurfaceControl leash)1303 TaskState(TransitionInfo.Change change, SurfaceControl leash) { 1304 mToken = change.getContainer(); 1305 mTaskInfo = change.getTaskInfo(); 1306 mTaskSurface = change.getLeash(); 1307 mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; 1308 mLeash = leash; 1309 } 1310 indexOf(ArrayList<TaskState> list, TransitionInfo.Change change)1311 static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) { 1312 for (int i = list.size() - 1; i >= 0; --i) { 1313 if (list.get(i).mToken.equals(change.getContainer())) { 1314 return i; 1315 } 1316 } 1317 return -1; 1318 } 1319 isLeaf()1320 boolean isLeaf() { 1321 return mLeash != null; 1322 } 1323 toString()1324 public String toString() { 1325 return "" + mToken + " : " + mLeash; 1326 } 1327 } 1328 1329 /** 1330 * An interface for a mixed handler to receive information about recents requests (since these 1331 * come into this handler directly vs from WMCore request). 1332 */ 1333 public interface RecentsMixedHandler extends Transitions.TransitionHandler { 1334 /** 1335 * Called when a recents request comes in. The handler can add operations to outWCT. If 1336 * the handler wants to "accept" the transition, it should return a Consumer accepting the 1337 * IBinder for the transition. If not, it should return `null`. 1338 * 1339 * If a mixed-handler accepts this recents, it will be the de-facto handler for this 1340 * transition and is required to call the associated {@link #startAnimation}, 1341 * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. 1342 */ 1343 @Nullable handleRecentsRequest(WindowContainerTransaction outWCT)1344 Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT); 1345 } 1346 } 1347