1 /* 2 * Copyright (C) 2017 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.server.wm; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 22 import static android.view.RemoteAnimationTarget.MODE_OPENING; 23 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; 24 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 25 26 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; 27 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; 28 import static com.android.server.wm.AnimationAdapterProto.REMOTE; 29 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; 30 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; 31 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; 32 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; 33 34 import android.annotation.IntDef; 35 import android.annotation.NonNull; 36 import android.app.ActivityManager.TaskSnapshot; 37 import android.app.WindowConfiguration; 38 import android.graphics.Point; 39 import android.graphics.Rect; 40 import android.os.Binder; 41 import android.os.IBinder.DeathRecipient; 42 import android.os.RemoteException; 43 import android.os.SystemClock; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.IntArray; 47 import android.util.Slog; 48 import android.util.SparseBooleanArray; 49 import android.util.proto.ProtoOutputStream; 50 import android.view.IRecentsAnimationController; 51 import android.view.IRecentsAnimationRunner; 52 import android.view.InputWindowHandle; 53 import android.view.RemoteAnimationTarget; 54 import android.view.SurfaceControl; 55 import android.view.SurfaceControl.Transaction; 56 57 import com.android.internal.annotations.VisibleForTesting; 58 import com.android.internal.inputmethod.SoftInputShowHideReason; 59 import com.android.internal.util.function.pooled.PooledConsumer; 60 import com.android.internal.util.function.pooled.PooledFunction; 61 import com.android.internal.util.function.pooled.PooledLambda; 62 import com.android.server.LocalServices; 63 import com.android.server.inputmethod.InputMethodManagerInternal; 64 import com.android.server.protolog.common.ProtoLog; 65 import com.android.server.statusbar.StatusBarManagerInternal; 66 import com.android.server.wm.SurfaceAnimator.AnimationType; 67 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; 68 import com.android.server.wm.utils.InsetUtils; 69 70 import com.google.android.collect.Sets; 71 72 import java.io.PrintWriter; 73 import java.util.ArrayList; 74 import java.util.stream.Collectors; 75 76 /** 77 * Controls a single instance of the remote driven recents animation. In particular, this allows 78 * the calling SystemUI to animate the visible task windows as a part of the transition. The remote 79 * runner is provided an animation controller which allows it to take screenshots and to notify 80 * window manager when the animation is completed. In addition, window manager may also notify the 81 * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.) 82 */ 83 public class RecentsAnimationController implements DeathRecipient { 84 private static final String TAG = RecentsAnimationController.class.getSimpleName(); 85 private static final long FAILSAFE_DELAY = 1000; 86 87 public static final int REORDER_KEEP_IN_PLACE = 0; 88 public static final int REORDER_MOVE_TO_TOP = 1; 89 public static final int REORDER_MOVE_TO_ORIGINAL_POSITION = 2; 90 91 @IntDef(prefix = { "REORDER_MODE_" }, value = { 92 REORDER_KEEP_IN_PLACE, 93 REORDER_MOVE_TO_TOP, 94 REORDER_MOVE_TO_ORIGINAL_POSITION 95 }) 96 public @interface ReorderMode {} 97 98 private final WindowManagerService mService; 99 private final StatusBarManagerInternal mStatusBar; 100 private IRecentsAnimationRunner mRunner; 101 private final RecentsAnimationCallbacks mCallbacks; 102 private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); 103 private final IntArray mPendingNewTaskTargets = new IntArray(0); 104 105 private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations = 106 new ArrayList<>(); 107 private final int mDisplayId; 108 private boolean mWillFinishToHome = false; 109 private final Runnable mFailsafeRunnable = () -> cancelAnimation( 110 mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION, 111 "failSafeRunnable"); 112 113 // The recents component app token that is shown behind the visibile tasks 114 private ActivityRecord mTargetActivityRecord; 115 private DisplayContent mDisplayContent; 116 private int mTargetActivityType; 117 private Rect mMinimizedHomeBounds = new Rect(); 118 119 // We start the RecentsAnimationController in a pending-start state since we need to wait for 120 // the wallpaper/activity to draw before we can give control to the handler to start animating 121 // the visible task surfaces 122 private boolean mPendingStart = true; 123 124 // Set when the animation has been canceled 125 private boolean mCanceled; 126 127 // Whether or not the input consumer is enabled. The input consumer must be both registered and 128 // enabled for it to start intercepting touch events. 129 private boolean mInputConsumerEnabled; 130 131 private final Rect mTmpRect = new Rect(); 132 133 private boolean mLinkedToDeathOfRunner; 134 135 // Whether to try to defer canceling from a stack order change until the next transition 136 private boolean mRequestDeferCancelUntilNextTransition; 137 // Whether to actually defer canceling until the next transition 138 private boolean mCancelOnNextTransitionStart; 139 // Whether to take a screenshot when handling a deferred cancel 140 private boolean mCancelDeferredWithScreenshot; 141 142 /** 143 * Animates the screenshot of task that used to be controlled by RecentsAnimation. 144 * @see {@link #setCancelOnNextTransitionStart} 145 */ 146 SurfaceAnimator mRecentScreenshotAnimator; 147 148 /** 149 * An app transition listener to cancel the recents animation only after the app transition 150 * starts or is canceled. 151 */ 152 final AppTransitionListener mAppTransitionListener = new AppTransitionListener() { 153 @Override 154 public int onAppTransitionStartingLocked(int transit, long duration, 155 long statusBarAnimationStartTime, long statusBarAnimationDuration) { 156 continueDeferredCancel(); 157 return 0; 158 } 159 160 @Override 161 public void onAppTransitionCancelledLocked(int transit) { 162 continueDeferredCancel(); 163 } 164 165 private void continueDeferredCancel() { 166 mDisplayContent.mAppTransition.unregisterListener(this); 167 if (mCanceled) { 168 return; 169 } 170 171 if (mCancelOnNextTransitionStart) { 172 mCancelOnNextTransitionStart = false; 173 cancelAnimationWithScreenshot(mCancelDeferredWithScreenshot); 174 } 175 } 176 }; 177 178 public interface RecentsAnimationCallbacks { 179 /** Callback when recents animation is finished. */ onAnimationFinished(@eorderMode int reorderMode, boolean sendUserLeaveHint)180 void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint); 181 } 182 183 private final IRecentsAnimationController mController = 184 new IRecentsAnimationController.Stub() { 185 186 @Override 187 public TaskSnapshot screenshotTask(int taskId) { 188 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 189 "screenshotTask(%d): mCanceled=%b", taskId, mCanceled); 190 final long token = Binder.clearCallingIdentity(); 191 try { 192 synchronized (mService.getWindowManagerLock()) { 193 if (mCanceled) { 194 return null; 195 } 196 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 197 final TaskAnimationAdapter adapter = mPendingAnimations.get(i); 198 final Task task = adapter.mTask; 199 if (task.mTaskId == taskId) { 200 final TaskSnapshotController snapshotController = 201 mService.mTaskSnapshotController; 202 final ArraySet<Task> tasks = Sets.newArraySet(task); 203 snapshotController.snapshotTasks(tasks); 204 snapshotController.addSkipClosingAppSnapshotTasks(tasks); 205 return snapshotController.getSnapshot(taskId, 0 /* userId */, 206 false /* restoreFromDisk */, false /* isLowResolution */); 207 } 208 } 209 return null; 210 } 211 } finally { 212 Binder.restoreCallingIdentity(token); 213 } 214 } 215 216 @Override 217 public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint) { 218 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 219 "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled); 220 final long token = Binder.clearCallingIdentity(); 221 try { 222 synchronized (mService.getWindowManagerLock()) { 223 if (mCanceled) { 224 return; 225 } 226 // Remove all new task targets. 227 for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { 228 removeTaskInternal(mPendingNewTaskTargets.get(i)); 229 } 230 } 231 232 // Note, the callback will handle its own synchronization, do not lock on WM lock 233 // prior to calling the callback 234 mCallbacks.onAnimationFinished(moveHomeToTop 235 ? REORDER_MOVE_TO_TOP 236 : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint); 237 } finally { 238 Binder.restoreCallingIdentity(token); 239 } 240 } 241 242 @Override 243 public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) 244 throws RemoteException { 245 final long token = Binder.clearCallingIdentity(); 246 try { 247 synchronized (mService.getWindowManagerLock()) { 248 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 249 final Task task = mPendingAnimations.get(i).mTask; 250 if (task.getActivityType() != mTargetActivityType) { 251 task.setCanAffectSystemUiFlags(behindSystemBars); 252 } 253 } 254 mService.mWindowPlacerLocked.requestTraversal(); 255 } 256 } finally { 257 Binder.restoreCallingIdentity(token); 258 } 259 } 260 261 @Override 262 public void setInputConsumerEnabled(boolean enabled) { 263 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 264 "setInputConsumerEnabled(%s): mCanceled=%b", enabled, mCanceled); 265 final long token = Binder.clearCallingIdentity(); 266 try { 267 synchronized (mService.getWindowManagerLock()) { 268 if (mCanceled) { 269 return; 270 } 271 272 mInputConsumerEnabled = enabled; 273 final InputMonitor inputMonitor = mDisplayContent.getInputMonitor(); 274 inputMonitor.updateInputWindowsLw(true /*force*/); 275 mService.scheduleAnimationLocked(); 276 } 277 } finally { 278 Binder.restoreCallingIdentity(token); 279 } 280 } 281 282 @Override 283 public void hideCurrentInputMethod() { 284 final long token = Binder.clearCallingIdentity(); 285 try { 286 final InputMethodManagerInternal inputMethodManagerInternal = 287 LocalServices.getService(InputMethodManagerInternal.class); 288 if (inputMethodManagerInternal != null) { 289 inputMethodManagerInternal.hideCurrentInputMethod( 290 SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); 291 } 292 } finally { 293 Binder.restoreCallingIdentity(token); 294 } 295 } 296 297 @Override 298 public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { 299 synchronized (mService.mGlobalLock) { 300 setDeferredCancel(defer, screenshot); 301 } 302 } 303 304 @Override 305 public void cleanupScreenshot() { 306 synchronized (mService.mGlobalLock) { 307 if (mRecentScreenshotAnimator != null) { 308 mRecentScreenshotAnimator.cancelAnimation(); 309 mRecentScreenshotAnimator = null; 310 } 311 } 312 } 313 314 @Override 315 public void setWillFinishToHome(boolean willFinishToHome) { 316 synchronized (mService.getWindowManagerLock()) { 317 mWillFinishToHome = willFinishToHome; 318 } 319 } 320 321 @Override 322 public boolean removeTask(int taskId) { 323 final long token = Binder.clearCallingIdentity(); 324 try { 325 synchronized (mService.getWindowManagerLock()) { 326 return removeTaskInternal(taskId); 327 } 328 } finally { 329 Binder.restoreCallingIdentity(token); 330 } 331 } 332 }; 333 334 /** 335 * @param remoteAnimationRunner The remote runner which should be notified when the animation is 336 * ready to start or has been canceled 337 * @param callbacks Callbacks to be made when the animation finishes 338 */ RecentsAnimationController(WindowManagerService service, IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, int displayId)339 RecentsAnimationController(WindowManagerService service, 340 IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, 341 int displayId) { 342 mService = service; 343 mRunner = remoteAnimationRunner; 344 mCallbacks = callbacks; 345 mDisplayId = displayId; 346 mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); 347 mDisplayContent = service.mRoot.getDisplayContent(displayId); 348 } 349 350 /** 351 * Initializes the recents animation controller. This is a separate call from the constructor 352 * because it may call cancelAnimation() which needs to properly clean up the controller 353 * in the window manager. 354 */ initialize(int targetActivityType, SparseBooleanArray recentTaskIds, ActivityRecord targetActivity)355 public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds, 356 ActivityRecord targetActivity) { 357 mTargetActivityType = targetActivityType; 358 mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener); 359 360 // Make leashes for each of the visible/target tasks and add it to the recents animation to 361 // be started 362 // TODO(b/153090560): Support Recents on multiple task display areas 363 final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea() 364 .getVisibleTasks(); 365 final ActivityStack targetStack = mDisplayContent.getDefaultTaskDisplayArea() 366 .getStack(WINDOWING_MODE_UNDEFINED, targetActivityType); 367 if (targetStack != null) { 368 final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) -> 369 { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class), 370 visibleTasks); 371 targetStack.forAllLeafTasks(c, true /* traverseTopToBottom */); 372 c.recycle(); 373 } 374 375 final int taskCount = visibleTasks.size(); 376 for (int i = 0; i < taskCount; i++) { 377 final Task task = visibleTasks.get(i); 378 final WindowConfiguration config = task.getWindowConfiguration(); 379 if (config.tasksAreFloating() 380 || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { 381 continue; 382 } 383 addAnimation(task, !recentTaskIds.get(task.mTaskId)); 384 } 385 386 // Skip the animation if there is nothing to animate 387 if (mPendingAnimations.isEmpty()) { 388 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks"); 389 return; 390 } 391 392 try { 393 linkToDeathOfRunner(); 394 } catch (RemoteException e) { 395 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath"); 396 return; 397 } 398 399 // Adjust the wallpaper visibility for the showing target activity 400 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 401 "setHomeApp(%s)", targetActivity.getName()); 402 mTargetActivityRecord = targetActivity; 403 if (targetActivity.windowsCanBeWallpaperTarget()) { 404 mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; 405 mDisplayContent.setLayoutNeeded(); 406 } 407 408 // Save the minimized home height 409 final ActivityStack rootHomeTask = 410 mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); 411 mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null; 412 413 mService.mWindowPlacerLocked.performSurfacePlacement(); 414 415 mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); 416 417 // Notify that the animation has started 418 if (mStatusBar != null) { 419 mStatusBar.onRecentsAnimationStateChanged(true /* running */); 420 } 421 } 422 423 @VisibleForTesting addAnimation(Task task, boolean isRecentTaskInvisible)424 AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { 425 return addAnimation(task, isRecentTaskInvisible, false /* hidden */, 426 null /* finishedCallback */); 427 } 428 429 @VisibleForTesting addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden, OnAnimationFinishedCallback finishedCallback)430 AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden, 431 OnAnimationFinishedCallback finishedCallback) { 432 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); 433 final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, 434 isRecentTaskInvisible); 435 task.startAnimation(task.getPendingTransaction(), taskAdapter, hidden, 436 ANIMATION_TYPE_RECENTS, finishedCallback); 437 task.commitPendingTransaction(); 438 mPendingAnimations.add(taskAdapter); 439 return taskAdapter; 440 } 441 442 @VisibleForTesting removeAnimation(TaskAnimationAdapter taskAdapter)443 void removeAnimation(TaskAnimationAdapter taskAdapter) { 444 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 445 "removeAnimation(%d)", taskAdapter.mTask.mTaskId); 446 taskAdapter.mTask.setCanAffectSystemUiFlags(true); 447 taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType, 448 taskAdapter); 449 mPendingAnimations.remove(taskAdapter); 450 } 451 452 @VisibleForTesting removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter)453 void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) { 454 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()"); 455 wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished( 456 wallpaperAdapter.getLastAnimationType(), wallpaperAdapter); 457 mPendingWallpaperAnimations.remove(wallpaperAdapter); 458 } 459 startAnimation()460 void startAnimation() { 461 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 462 "startAnimation(): mPendingStart=%b mCanceled=%b", mPendingStart, mCanceled); 463 if (!mPendingStart || mCanceled) { 464 // Skip starting if we've already started or canceled the animation 465 return; 466 } 467 try { 468 // Create the app targets 469 final RemoteAnimationTarget[] appTargets = createAppAnimations(); 470 471 // Skip the animation if there is nothing to animate 472 if (appTargets.length == 0) { 473 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows"); 474 return; 475 } 476 477 // Create the wallpaper targets 478 final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); 479 480 mPendingStart = false; 481 482 // Perform layout if it was scheduled before to make sure that we get correct content 483 // insets for the target app window after a rotation 484 mDisplayContent.performLayout(false /* initial */, false /* updateInputWindows */); 485 486 final Rect minimizedHomeBounds = mTargetActivityRecord != null 487 && mTargetActivityRecord.inSplitScreenSecondaryWindowingMode() 488 ? mMinimizedHomeBounds 489 : null; 490 final Rect contentInsets; 491 if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) { 492 contentInsets = mTargetActivityRecord.findMainWindow().getContentInsets(); 493 } else { 494 // If the window for the activity had not yet been created, use the display insets. 495 mService.getStableInsets(mDisplayId, mTmpRect); 496 contentInsets = mTmpRect; 497 } 498 mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets, 499 minimizedHomeBounds); 500 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 501 "startAnimation(): Notify animation start: %s", 502 mPendingAnimations.stream() 503 .map(anim->anim.mTask.mTaskId).collect(Collectors.toList())); 504 } catch (RemoteException e) { 505 Slog.e(TAG, "Failed to start recents animation", e); 506 } 507 508 if (mTargetActivityRecord != null) { 509 final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(1); 510 reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM); 511 mService.mAtmService.mStackSupervisor.getActivityMetricsLogger() 512 .notifyTransitionStarting(reasons); 513 } 514 } 515 addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback)516 void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) { 517 if (mRunner != null) { 518 // No need to send task appeared when the task target already exists. 519 if (isAnimatingTask(task)) { 520 return; 521 } 522 final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback); 523 if (target == null) { 524 return; 525 } 526 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); 527 try { 528 mRunner.onTaskAppeared(target); 529 } catch (RemoteException e) { 530 Slog.e(TAG, "Failed to report task appeared", e); 531 } 532 } 533 } 534 createTaskRemoteAnimation(Task task, OnAnimationFinishedCallback finishedCallback)535 private RemoteAnimationTarget createTaskRemoteAnimation(Task task, 536 OnAnimationFinishedCallback finishedCallback) { 537 final SparseBooleanArray recentTaskIds = 538 mService.mAtmService.getRecentTasks().getRecentTaskIds(); 539 TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, 540 !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback); 541 mPendingNewTaskTargets.add(task.mTaskId); 542 return adapter.createRemoteAnimationTarget(); 543 } 544 removeTaskInternal(int taskId)545 private boolean removeTaskInternal(int taskId) { 546 boolean result = false; 547 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 548 // Only allows when task target has became visible to user, to prevent 549 // the flickering during remove animation and task visible. 550 final TaskAnimationAdapter target = mPendingAnimations.get(i); 551 if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) { 552 removeAnimation(target); 553 final int taskIndex = mPendingNewTaskTargets.indexOf(taskId); 554 if (taskIndex != -1) { 555 mPendingNewTaskTargets.remove(taskIndex); 556 } 557 result = true; 558 break; 559 } 560 } 561 return result; 562 } 563 createAppAnimations()564 private RemoteAnimationTarget[] createAppAnimations() { 565 final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); 566 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 567 final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); 568 final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget(); 569 if (target != null) { 570 targets.add(target); 571 } else { 572 removeAnimation(taskAdapter); 573 } 574 } 575 return targets.toArray(new RemoteAnimationTarget[targets.size()]); 576 } 577 createWallpaperAnimations()578 private RemoteAnimationTarget[] createWallpaperAnimations() { 579 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()"); 580 return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L, 581 adapter -> { 582 synchronized (mService.mGlobalLock) { 583 // If the wallpaper animation is canceled, continue with the recents 584 // animation 585 mPendingWallpaperAnimations.remove(adapter); 586 } 587 }, mPendingWallpaperAnimations); 588 } 589 590 void cancelAnimation(@ReorderMode int reorderMode, String reason) { 591 cancelAnimation(reorderMode, false /*screenshot */, reason); 592 } 593 594 void cancelAnimationWithScreenshot(boolean screenshot) { 595 cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "stackOrderChanged"); 596 } 597 598 private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) { 599 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "cancelAnimation(): reason=%s", reason); 600 synchronized (mService.getWindowManagerLock()) { 601 if (mCanceled) { 602 // We've already canceled the animation 603 return; 604 } 605 mService.mH.removeCallbacks(mFailsafeRunnable); 606 mCanceled = true; 607 608 if (screenshot) { 609 // Screen shot previous task when next task starts transition and notify the runner. 610 // We will actually finish the animation once the runner calls cleanUpScreenshot(). 611 final Task task = mPendingAnimations.get(0).mTask; 612 final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode); 613 try { 614 mRunner.onAnimationCanceled(taskSnapshot); 615 } catch (RemoteException e) { 616 Slog.e(TAG, "Failed to cancel recents animation", e); 617 } 618 if (taskSnapshot == null) { 619 mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); 620 } 621 } else { 622 // Otherwise, notify the runner and clean up the animation immediately 623 // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls 624 // to the runner if we this actually triggers cancel twice on the caller 625 try { 626 mRunner.onAnimationCanceled(null /* taskSnapshot */); 627 } catch (RemoteException e) { 628 Slog.e(TAG, "Failed to cancel recents animation", e); 629 } 630 mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); 631 } 632 } 633 } 634 635 /** 636 * Cancel recents animation when the next app transition starts. 637 * <p> 638 * When we cancel the recents animation due to a stack order change, we can't just cancel it 639 * immediately as it would lead to a flicker in Launcher if we just remove the task from the 640 * leash. Instead we screenshot the previous task and replace the child of the leash with the 641 * screenshot, so that Launcher can still control the leash lifecycle & make the next app 642 * transition animate smoothly without flickering. 643 */ 644 void setCancelOnNextTransitionStart() { 645 mCancelOnNextTransitionStart = true; 646 } 647 648 /** 649 * Requests that we attempt to defer the cancel until the next app transition if we are 650 * canceling from a stack order change. If {@param screenshot} is specified, then the system 651 * will replace the contents of the leash with a screenshot, which must be cleaned up when the 652 * runner calls cleanUpScreenshot(). 653 */ 654 void setDeferredCancel(boolean defer, boolean screenshot) { 655 mRequestDeferCancelUntilNextTransition = defer; 656 mCancelDeferredWithScreenshot = screenshot; 657 } 658 659 /** 660 * @return Whether we should defer the cancel from a stack order change until the next app 661 * transition. 662 */ 663 boolean shouldDeferCancelUntilNextTransition() { 664 return mRequestDeferCancelUntilNextTransition; 665 } 666 667 /** 668 * @return Whether we should both defer the cancel from a stack order change until the next 669 * app transition, and also that the deferred cancel should replace the contents of the leash 670 * with a screenshot. 671 */ 672 boolean shouldDeferCancelWithScreenshot() { 673 return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot; 674 } 675 676 TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode) { 677 final TaskSnapshotController snapshotController = mService.mTaskSnapshotController; 678 final ArraySet<Task> tasks = Sets.newArraySet(task); 679 snapshotController.snapshotTasks(tasks); 680 snapshotController.addSkipClosingAppSnapshotTasks(tasks); 681 final TaskSnapshot taskSnapshot = snapshotController.getSnapshot(task.mTaskId, 682 task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */); 683 if (taskSnapshot == null) { 684 return null; 685 } 686 687 final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(mService.mSurfaceControlFactory, task, 688 new SurfaceControl.ScreenshotGraphicBuffer(taskSnapshot.getSnapshot(), 689 taskSnapshot.getColorSpace(), false /* containsSecureLayers */)); 690 mRecentScreenshotAnimator = new SurfaceAnimator( 691 animatable, 692 (type, anim) -> { 693 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "mRecentScreenshotAnimator finish"); 694 mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); 695 }, mService); 696 mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator); 697 return taskSnapshot; 698 } 699 700 void cleanupAnimation(@ReorderMode int reorderMode) { 701 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, 702 "cleanupAnimation(): Notify animation finished mPendingAnimations=%d " 703 + "reorderMode=%d", 704 mPendingAnimations.size(), reorderMode); 705 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 706 final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); 707 if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) { 708 taskAdapter.mTask.dontAnimateDimExit(); 709 } 710 removeAnimation(taskAdapter); 711 } 712 713 for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { 714 final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i); 715 removeWallpaperAnimation(wallpaperAdapter); 716 } 717 718 // Clear any pending failsafe runnables 719 mService.mH.removeCallbacks(mFailsafeRunnable); 720 mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener); 721 722 // Clear references to the runner 723 unlinkToDeathOfRunner(); 724 mRunner = null; 725 mCanceled = true; 726 727 // Make sure previous animator has cleaned-up. 728 if (mRecentScreenshotAnimator != null) { 729 mRecentScreenshotAnimator.cancelAnimation(); 730 mRecentScreenshotAnimator = null; 731 } 732 733 // Update the input windows after the animation is complete 734 final InputMonitor inputMonitor = mDisplayContent.getInputMonitor(); 735 inputMonitor.updateInputWindowsLw(true /*force*/); 736 737 // We have deferred all notifications to the target app as a part of the recents animation, 738 // so if we are actually transitioning there, notify again here 739 if (mTargetActivityRecord != null) { 740 if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) { 741 mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked( 742 mTargetActivityRecord.token); 743 } 744 } 745 mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation( 746 reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION /* moveRecentsToBack */); 747 748 // Notify that the animation has ended 749 if (mStatusBar != null) { 750 mStatusBar.onRecentsAnimationStateChanged(false /* running */); 751 } 752 } 753 754 void scheduleFailsafe() { 755 mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY); 756 } 757 758 private void linkToDeathOfRunner() throws RemoteException { 759 if (!mLinkedToDeathOfRunner) { 760 mRunner.asBinder().linkToDeath(this, 0); 761 mLinkedToDeathOfRunner = true; 762 } 763 } 764 765 private void unlinkToDeathOfRunner() { 766 if (mLinkedToDeathOfRunner) { 767 mRunner.asBinder().unlinkToDeath(this, 0); 768 mLinkedToDeathOfRunner = false; 769 } 770 } 771 772 @Override 773 public void binderDied() { 774 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied"); 775 776 synchronized (mService.getWindowManagerLock()) { 777 // Clear associated input consumers on runner death 778 final InputMonitor inputMonitor = mDisplayContent.getInputMonitor(); 779 inputMonitor.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); 780 } 781 } 782 783 void checkAnimationReady(WallpaperController wallpaperController) { 784 if (mPendingStart) { 785 final boolean wallpaperReady = !isTargetOverWallpaper() 786 || (wallpaperController.getWallpaperTarget() != null 787 && wallpaperController.wallpaperTransitionReady()); 788 if (wallpaperReady) { 789 mService.getRecentsAnimationController().startAnimation(); 790 } 791 } 792 } 793 794 boolean isWallpaperVisible(WindowState w) { 795 return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION && 796 ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord) 797 || isAnimatingTask(w.getTask())) 798 && isTargetOverWallpaper(); 799 } 800 801 /** 802 * @return Whether to use the input consumer to override app input to route home/recents. 803 */ 804 boolean shouldApplyInputConsumer(ActivityRecord activity) { 805 // Only apply the input consumer if it is enabled, it is not the target (home/recents) 806 // being revealed with the transition, and we are actively animating the app as a part of 807 // the animation 808 return mInputConsumerEnabled && activity != null 809 && !isTargetApp(activity) && isAnimatingApp(activity); 810 } 811 812 boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle, 813 boolean hasFocus) { 814 // Update the input consumer touchable region to match the target app main window 815 final WindowState targetAppMainWindow = mTargetActivityRecord != null 816 ? mTargetActivityRecord.findMainWindow() 817 : null; 818 if (targetAppMainWindow != null) { 819 targetAppMainWindow.getBounds(mTmpRect); 820 inputWindowHandle.hasFocus = hasFocus; 821 inputWindowHandle.touchableRegion.set(mTmpRect); 822 return true; 823 } 824 return false; 825 } 826 827 boolean isTargetApp(ActivityRecord activity) { 828 return mTargetActivityRecord != null && activity == mTargetActivityRecord; 829 } 830 831 private boolean isTargetOverWallpaper() { 832 if (mTargetActivityRecord == null) { 833 return false; 834 } 835 return mTargetActivityRecord.windowsCanBeWallpaperTarget(); 836 } 837 838 boolean isAnimatingTask(Task task) { 839 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 840 if (task == mPendingAnimations.get(i).mTask) { 841 return true; 842 } 843 } 844 return false; 845 } 846 847 boolean isAnimatingWallpaper(WallpaperWindowToken token) { 848 for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { 849 if (token == mPendingWallpaperAnimations.get(i).getToken()) { 850 return true; 851 } 852 } 853 return false; 854 } 855 856 private boolean isAnimatingApp(ActivityRecord activity) { 857 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 858 final Task task = mPendingAnimations.get(i).mTask; 859 final PooledFunction f = PooledLambda.obtainFunction( 860 (a, b) -> a == b, activity, 861 PooledLambda.__(ActivityRecord.class)); 862 boolean isAnimatingApp = task.forAllActivities(f); 863 f.recycle(); 864 if (isAnimatingApp) { 865 return true; 866 } 867 } 868 return false; 869 } 870 871 boolean shouldIgnoreForAccessibility(WindowState windowState) { 872 final Task task = windowState.getTask(); 873 return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mActivityRecord); 874 } 875 876 /** 877 * If the animation target ActivityRecord has a fixed rotation ({@link 878 * WindowToken#hasFixedRotationTransform()}, the provided wallpaper will be rotated accordingly. 879 * 880 * This avoids any screen rotation animation when animating to the Recents view. 881 */ 882 void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) { 883 if (mTargetActivityRecord == null) { 884 return; 885 } 886 wallpaper.linkFixedRotationTransform(mTargetActivityRecord); 887 } 888 889 @VisibleForTesting 890 class TaskAnimationAdapter implements AnimationAdapter { 891 892 private final Task mTask; 893 private SurfaceControl mCapturedLeash; 894 private OnAnimationFinishedCallback mCapturedFinishCallback; 895 private @AnimationType int mLastAnimationType; 896 private final boolean mIsRecentTaskInvisible; 897 private RemoteAnimationTarget mTarget; 898 private final Rect mBounds = new Rect(); 899 // The bounds of the target relative to its parent. 900 private Rect mLocalBounds = new Rect(); 901 902 TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) { 903 mTask = task; 904 mIsRecentTaskInvisible = isRecentTaskInvisible; 905 mBounds.set(mTask.getBounds()); 906 907 mLocalBounds.set(mBounds); 908 Point tmpPos = new Point(); 909 mTask.getRelativePosition(tmpPos); 910 mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); 911 } 912 913 RemoteAnimationTarget createRemoteAnimationTarget() { 914 final ActivityRecord topApp = mTask.getTopVisibleActivity(); 915 final WindowState mainWindow = topApp != null 916 ? topApp.findMainWindow() 917 : null; 918 if (mainWindow == null) { 919 return null; 920 } 921 final Rect insets = new Rect(); 922 mainWindow.getContentInsets(insets); 923 InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets()); 924 final int mode = topApp.getActivityType() == mTargetActivityType 925 ? MODE_OPENING 926 : MODE_CLOSING; 927 mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, 928 !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, 929 insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), 930 mLocalBounds, mBounds, mTask.getWindowConfiguration(), 931 mIsRecentTaskInvisible, null, null); 932 return mTarget; 933 } 934 935 @Override 936 public boolean getShowWallpaper() { 937 return false; 938 } 939 940 @Override 941 public void startAnimation(SurfaceControl animationLeash, Transaction t, 942 @AnimationType int type, OnAnimationFinishedCallback finishCallback) { 943 // Restore position and stack crop until client has a chance to modify it. 944 t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top); 945 mTmpRect.set(mLocalBounds); 946 mTmpRect.offsetTo(0, 0); 947 t.setWindowCrop(animationLeash, mTmpRect); 948 mCapturedLeash = animationLeash; 949 mCapturedFinishCallback = finishCallback; 950 mLastAnimationType = type; 951 } 952 953 @Override 954 public void onAnimationCancelled(SurfaceControl animationLeash) { 955 // Cancel the animation immediately if any single task animator is canceled 956 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled"); 957 } 958 959 @Override 960 public long getDurationHint() { 961 return 0; 962 } 963 964 @Override 965 public long getStatusBarTransitionsStartTime() { 966 return SystemClock.uptimeMillis(); 967 } 968 969 @Override 970 public void dump(PrintWriter pw, String prefix) { 971 pw.print(prefix); pw.println("task=" + mTask); 972 if (mTarget != null) { 973 pw.print(prefix); pw.println("Target:"); 974 mTarget.dump(pw, prefix + " "); 975 } else { 976 pw.print(prefix); pw.println("Target: null"); 977 } 978 pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible); 979 pw.println("mLocalBounds=" + mLocalBounds); 980 pw.println("mBounds=" + mBounds); 981 pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible); 982 } 983 984 @Override 985 public void dumpDebug(ProtoOutputStream proto) { 986 final long token = proto.start(REMOTE); 987 if (mTarget != null) { 988 mTarget.dumpDebug(proto, TARGET); 989 } 990 proto.end(token); 991 } 992 } 993 994 public void dump(PrintWriter pw, String prefix) { 995 final String innerPrefix = prefix + " "; 996 pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":"); 997 pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart); 998 pw.print(innerPrefix); pw.println("mPendingAnimations=" + mPendingAnimations.size()); 999 pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled); 1000 pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled); 1001 pw.print(innerPrefix); pw.println("mTargetActivityRecord=" + mTargetActivityRecord); 1002 pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper()); 1003 pw.print(innerPrefix); pw.println("mRequestDeferCancelUntilNextTransition=" 1004 + mRequestDeferCancelUntilNextTransition); 1005 pw.print(innerPrefix); pw.println("mCancelOnNextTransitionStart=" 1006 + mCancelOnNextTransitionStart); 1007 pw.print(innerPrefix); pw.println("mCancelDeferredWithScreenshot=" 1008 + mCancelDeferredWithScreenshot); 1009 } 1010 } 1011