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