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