1 /* 2 * Copyright (C) 2016 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 com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; 20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.pm.PackageManager; 26 import android.graphics.Bitmap; 27 import android.graphics.PixelFormat; 28 import android.graphics.Point; 29 import android.graphics.RecordingCanvas; 30 import android.graphics.Rect; 31 import android.graphics.RenderNode; 32 import android.hardware.HardwareBuffer; 33 import android.os.Environment; 34 import android.os.Handler; 35 import android.util.ArraySet; 36 import android.util.Pair; 37 import android.util.Slog; 38 import android.view.Display; 39 import android.view.InsetsState; 40 import android.view.SurfaceControl; 41 import android.view.ThreadedRenderer; 42 import android.view.WindowInsets.Type; 43 import android.view.WindowInsetsController.Appearance; 44 import android.view.WindowManager.LayoutParams; 45 import android.window.TaskSnapshot; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.graphics.ColorUtils; 49 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; 50 import com.android.server.policy.WindowManagerPolicy.StartingSurface; 51 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter; 52 import com.android.server.wm.utils.InsetUtils; 53 54 import com.google.android.collect.Sets; 55 56 import java.io.PrintWriter; 57 import java.util.Set; 58 59 /** 60 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and 61 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we 62 * like without any copying. 63 * <p> 64 * System applications may retrieve a snapshot to represent the current state of a task, and draw 65 * them in their own process. 66 * <p> 67 * When we task becomes visible again, we show a starting window with the snapshot as the content to 68 * make app transitions more responsive. 69 * <p> 70 * To access this class, acquire the global window manager lock. 71 */ 72 class TaskSnapshotController { 73 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM; 74 75 /** 76 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be 77 * used as the snapshot. 78 */ 79 @VisibleForTesting 80 static final int SNAPSHOT_MODE_REAL = 0; 81 82 /** 83 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but 84 * we should try to use the app theme to create a fake representation of the app. 85 */ 86 @VisibleForTesting 87 static final int SNAPSHOT_MODE_APP_THEME = 1; 88 89 /** 90 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot. 91 */ 92 @VisibleForTesting 93 static final int SNAPSHOT_MODE_NONE = 2; 94 95 private final WindowManagerService mService; 96 97 private final TaskSnapshotCache mCache; 98 private final TaskSnapshotPersister mPersister; 99 private final TaskSnapshotLoader mLoader; 100 private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>(); 101 private final ArraySet<Task> mTmpTasks = new ArraySet<>(); 102 private final Handler mHandler = new Handler(); 103 private final float mHighResTaskSnapshotScale; 104 105 private final Rect mTmpRect = new Rect(); 106 107 /** 108 * Flag indicating whether we are running on an Android TV device. 109 */ 110 private final boolean mIsRunningOnTv; 111 112 /** 113 * Flag indicating whether we are running on an IoT device. 114 */ 115 private final boolean mIsRunningOnIoT; 116 117 /** 118 * Flag indicating whether we are running on an Android Wear device. 119 */ 120 private final boolean mIsRunningOnWear; 121 TaskSnapshotController(WindowManagerService service)122 TaskSnapshotController(WindowManagerService service) { 123 mService = service; 124 mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory); 125 mLoader = new TaskSnapshotLoader(mPersister); 126 mCache = new TaskSnapshotCache(mService, mLoader); 127 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature( 128 PackageManager.FEATURE_LEANBACK); 129 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature( 130 PackageManager.FEATURE_EMBEDDED); 131 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature( 132 PackageManager.FEATURE_WATCH); 133 mHighResTaskSnapshotScale = mService.mContext.getResources().getFloat( 134 com.android.internal.R.dimen.config_highResTaskSnapshotScale); 135 } 136 systemReady()137 void systemReady() { 138 mPersister.start(); 139 } 140 onTransitionStarting(DisplayContent displayContent)141 void onTransitionStarting(DisplayContent displayContent) { 142 handleClosingApps(displayContent.mClosingApps); 143 } 144 145 /** 146 * Called when the visibility of an app changes outside of the regular app transition flow. 147 */ notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible)148 void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) { 149 if (!visible) { 150 handleClosingApps(Sets.newArraySet(appWindowToken)); 151 } 152 } 153 handleClosingApps(ArraySet<ActivityRecord> closingApps)154 private void handleClosingApps(ArraySet<ActivityRecord> closingApps) { 155 if (shouldDisableSnapshots()) { 156 return; 157 } 158 // We need to take a snapshot of the task if and only if all activities of the task are 159 // either closing or hidden. 160 getClosingTasks(closingApps, mTmpTasks); 161 snapshotTasks(mTmpTasks); 162 mSkipClosingAppSnapshotTasks.clear(); 163 } 164 165 /** 166 * Adds the given {@param tasks} to the list of tasks which should not have their snapshots 167 * taken upon the next processing of the set of closing apps. The caller is responsible for 168 * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot. 169 */ 170 @VisibleForTesting addSkipClosingAppSnapshotTasks(Set<Task> tasks)171 void addSkipClosingAppSnapshotTasks(Set<Task> tasks) { 172 if (shouldDisableSnapshots()) { 173 return; 174 } 175 mSkipClosingAppSnapshotTasks.addAll(tasks); 176 } 177 snapshotTasks(ArraySet<Task> tasks)178 void snapshotTasks(ArraySet<Task> tasks) { 179 snapshotTasks(tasks, false /* allowSnapshotHome */); 180 } 181 recordTaskSnapshot(Task task, boolean allowSnapshotHome)182 void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { 183 final TaskSnapshot snapshot; 184 final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); 185 if (snapshotHome) { 186 snapshot = snapshotTask(task); 187 } else { 188 switch (getSnapshotMode(task)) { 189 case SNAPSHOT_MODE_NONE: 190 return; 191 case SNAPSHOT_MODE_APP_THEME: 192 snapshot = drawAppThemeSnapshot(task); 193 break; 194 case SNAPSHOT_MODE_REAL: 195 snapshot = snapshotTask(task); 196 break; 197 default: 198 snapshot = null; 199 break; 200 } 201 } 202 if (snapshot != null) { 203 final HardwareBuffer buffer = snapshot.getHardwareBuffer(); 204 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { 205 buffer.close(); 206 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" 207 + buffer.getHeight()); 208 } else { 209 mCache.putSnapshot(task, snapshot); 210 // Don't persist or notify the change for the temporal snapshot. 211 if (!snapshotHome) { 212 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); 213 task.onSnapshotChanged(snapshot); 214 } 215 } 216 } 217 } 218 snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome)219 private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) { 220 for (int i = tasks.size() - 1; i >= 0; i--) { 221 recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome); 222 } 223 } 224 225 /** 226 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW 227 * MANAGER LOCK WHEN CALLING THIS METHOD! 228 */ 229 @Nullable getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean isLowResolution)230 TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk, 231 boolean isLowResolution) { 232 return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution 233 && mPersister.enableLowResSnapshots()); 234 } 235 236 /** 237 * @see WindowManagerInternal#clearSnapshotCache 238 */ clearSnapshotCache()239 public void clearSnapshotCache() { 240 mCache.clearRunningCache(); 241 } 242 243 /** 244 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW 245 * MANAGER LOCK WHEN CALLING THIS METHOD! 246 */ createStartingSurface(ActivityRecord activity, TaskSnapshot snapshot)247 StartingSurface createStartingSurface(ActivityRecord activity, 248 TaskSnapshot snapshot) { 249 return TaskSnapshotSurface.create(mService, activity, snapshot); 250 } 251 252 /** 253 * Find the window for a given task to take a snapshot. Top child of the task is usually the one 254 * we're looking for, but during app transitions, trampoline activities can appear in the 255 * children, which should be ignored. 256 */ findAppTokenForSnapshot(Task task)257 @Nullable private ActivityRecord findAppTokenForSnapshot(Task task) { 258 return task.getActivity((r) -> { 259 if (r == null || !r.isSurfaceShowing() || r.findMainWindow() == null) { 260 return false; 261 } 262 return r.forAllWindows( 263 // Ensure at least one window for the top app is visible before attempting to 264 // take a screenshot. Visible here means that the WSA surface is shown and has 265 // an alpha greater than 0. 266 ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown() 267 && ws.mWinAnimator.mLastAlpha > 0f, true /* traverseTopToBottom */); 268 269 }); 270 } 271 272 /** 273 * Validates the state of the Task is appropriate to capture a snapshot, collects 274 * information from the task and populates the builder. 275 * 276 * @param task the task to capture 277 * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to 278 * automatically select 279 * @param builder the snapshot builder to populate 280 * 281 * @return true if the state of the task is ok to proceed 282 */ 283 @VisibleForTesting 284 boolean prepareTaskSnapshot(Task task, int pixelFormat, TaskSnapshot.Builder builder) { 285 final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(task); 286 if (result == null) { 287 return false; 288 } 289 final ActivityRecord activity = result.first; 290 final WindowState mainWindow = result.second; 291 final Rect contentInsets = getSystemBarInsets(task.getBounds(), 292 mainWindow.getInsetsStateWithVisibilityOverride()); 293 final Rect letterboxInsets = activity.getLetterboxInsets(); 294 InsetUtils.addInsets(contentInsets, letterboxInsets); 295 296 builder.setIsRealSnapshot(true); 297 builder.setId(System.currentTimeMillis()); 298 builder.setContentInsets(contentInsets); 299 builder.setLetterboxInsets(letterboxInsets); 300 301 final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; 302 final boolean isShowWallpaper = mainWindow.hasWallpaper(); 303 304 if (pixelFormat == PixelFormat.UNKNOWN) { 305 pixelFormat = mPersister.use16BitFormat() && activity.fillsParent() 306 && !(isWindowTranslucent && isShowWallpaper) 307 ? PixelFormat.RGB_565 308 : PixelFormat.RGBA_8888; 309 } 310 311 final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat) 312 && (!activity.fillsParent() || isWindowTranslucent); 313 314 builder.setTopActivityComponent(activity.mActivityComponent); 315 builder.setPixelFormat(pixelFormat); 316 builder.setIsTranslucent(isTranslucent); 317 builder.setOrientation(activity.getTask().getConfiguration().orientation); 318 builder.setRotation(activity.getTask().getDisplayContent().getRotation()); 319 builder.setWindowingMode(task.getWindowingMode()); 320 builder.setAppearance(getAppearance(task)); 321 return true; 322 } 323 324 /** 325 * Check if the state of the Task is appropriate to capture a snapshot, such like the task 326 * snapshot or the associated IME surface snapshot. 327 * 328 * @param task the target task to capture the snapshot 329 * @return Pair of (the top activity of the task, the main window of the task) if passed the 330 * state checking. Returns {@code null} if the task state isn't ready to snapshot. 331 */ 332 Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(Task task) { 333 if (!mService.mPolicy.isScreenOn()) { 334 if (DEBUG_SCREENSHOT) { 335 Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); 336 } 337 return null; 338 } 339 final ActivityRecord activity = findAppTokenForSnapshot(task); 340 if (activity == null) { 341 if (DEBUG_SCREENSHOT) { 342 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task); 343 } 344 return null; 345 } 346 if (activity.hasCommittedReparentToAnimationLeash()) { 347 if (DEBUG_SCREENSHOT) { 348 Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity); 349 } 350 return null; 351 } 352 353 final WindowState mainWindow = activity.findMainWindow(); 354 if (mainWindow == null) { 355 Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task); 356 return null; 357 } 358 if (activity.hasFixedRotationTransform()) { 359 if (DEBUG_SCREENSHOT) { 360 Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity); 361 } 362 // The activity is in a temporal state that it has different rotation than the task. 363 return null; 364 } 365 return new Pair<>(activity, mainWindow); 366 } 367 368 @Nullable 369 SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, 370 TaskSnapshot.Builder builder) { 371 Point taskSize = new Point(); 372 final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task, 373 mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder); 374 builder.setTaskSize(taskSize); 375 return taskSnapshot; 376 } 377 378 @Nullable 379 SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, 380 float scaleFraction, TaskSnapshot.Builder builder) { 381 return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888, null, builder); 382 } 383 384 @Nullable 385 private SurfaceControl.ScreenshotHardwareBuffer createImeSnapshot(@NonNull Task task, 386 int pixelFormat) { 387 if (task.getSurfaceControl() == null) { 388 if (DEBUG_SCREENSHOT) { 389 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); 390 } 391 return null; 392 } 393 final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow; 394 SurfaceControl.ScreenshotHardwareBuffer imeBuffer = null; 395 if (imeWindow != null && imeWindow.isWinVisibleLw()) { 396 final Rect bounds = imeWindow.getContainingFrame(); 397 bounds.offsetTo(0, 0); 398 imeBuffer = SurfaceControl.captureLayersExcluding(imeWindow.getSurfaceControl(), 399 bounds, 1.0f, pixelFormat, null); 400 } 401 return imeBuffer; 402 } 403 404 /** 405 * Create the snapshot of the IME surface on the task which used for placing on the closing 406 * task to keep IME visibility while app transitioning. 407 */ 408 @Nullable 409 SurfaceControl.ScreenshotHardwareBuffer snapshotImeFromAttachedTask(@NonNull Task task) { 410 // Check if the IME targets task ready to take the corresponding IME snapshot, if not, 411 // means the task is not yet visible for some reasons and no need to snapshot IME surface. 412 if (checkIfReadyToSnapshot(task) == null) { 413 return null; 414 } 415 final int pixelFormat = mPersister.use16BitFormat() 416 ? PixelFormat.RGB_565 417 : PixelFormat.RGBA_8888; 418 return createImeSnapshot(task, pixelFormat); 419 } 420 421 @Nullable 422 SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, 423 float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) { 424 if (task.getSurfaceControl() == null) { 425 if (DEBUG_SCREENSHOT) { 426 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); 427 } 428 return null; 429 } 430 task.getBounds(mTmpRect); 431 mTmpRect.offsetTo(0, 0); 432 433 SurfaceControl[] excludeLayers; 434 final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow; 435 // Exclude IME window snapshot when IME isn't proper to attach to app. 436 final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null 437 && !task.getDisplayContent().shouldImeAttachedToApp(); 438 final WindowState navWindow = 439 task.getDisplayContent().getDisplayPolicy().getNavigationBar(); 440 // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the 441 // the swiped app when entering recent app, therefore the task will contain the navigation 442 // bar and we should exclude it from snapshot. 443 final boolean excludeNavBar = navWindow != null; 444 if (excludeIme && excludeNavBar) { 445 excludeLayers = new SurfaceControl[2]; 446 excludeLayers[0] = imeWindow.getSurfaceControl(); 447 excludeLayers[1] = navWindow.getSurfaceControl(); 448 } else if (excludeIme || excludeNavBar) { 449 excludeLayers = new SurfaceControl[1]; 450 excludeLayers[0] = 451 excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl(); 452 } else { 453 excludeLayers = new SurfaceControl[0]; 454 } 455 builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible()); 456 457 final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = 458 SurfaceControl.captureLayersExcluding( 459 task.getSurfaceControl(), mTmpRect, scaleFraction, 460 pixelFormat, excludeLayers); 461 if (outTaskSize != null) { 462 outTaskSize.x = mTmpRect.width(); 463 outTaskSize.y = mTmpRect.height(); 464 } 465 final HardwareBuffer buffer = screenshotBuffer == null ? null 466 : screenshotBuffer.getHardwareBuffer(); 467 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 468 return null; 469 } 470 return screenshotBuffer; 471 } 472 473 @Nullable 474 TaskSnapshot snapshotTask(Task task) { 475 return snapshotTask(task, PixelFormat.UNKNOWN); 476 } 477 478 @Nullable 479 TaskSnapshot snapshotTask(Task task, int pixelFormat) { 480 TaskSnapshot.Builder builder = new TaskSnapshot.Builder(); 481 482 if (!prepareTaskSnapshot(task, pixelFormat, builder)) { 483 // Failed some pre-req. Has been logged. 484 return null; 485 } 486 487 final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = 488 createTaskSnapshot(task, builder); 489 490 if (screenshotBuffer == null) { 491 // Failed to acquire image. Has been logged. 492 return null; 493 } 494 builder.setSnapshot(screenshotBuffer.getHardwareBuffer()); 495 builder.setColorSpace(screenshotBuffer.getColorSpace()); 496 return builder.build(); 497 } 498 499 boolean shouldDisableSnapshots() { 500 return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT; 501 } 502 503 /** 504 * Retrieves all closing tasks based on the list of closing apps during an app transition. 505 */ 506 @VisibleForTesting 507 void getClosingTasks(ArraySet<ActivityRecord> closingApps, ArraySet<Task> outClosingTasks) { 508 outClosingTasks.clear(); 509 for (int i = closingApps.size() - 1; i >= 0; i--) { 510 final ActivityRecord activity = closingApps.valueAt(i); 511 final Task task = activity.getTask(); 512 if (task == null) continue; 513 514 // Since RecentsAnimation will handle task snapshot while switching apps with the 515 // best capture timing (e.g. IME window capture), 516 // No need additional task capture while task is controlled by RecentsAnimation. 517 if (task.isAnimatingByRecents()) { 518 mSkipClosingAppSnapshotTasks.add(task); 519 } 520 // If the task of the app is not visible anymore, it means no other app in that task 521 // is opening. Thus, the task is closing. 522 if (!task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) { 523 outClosingTasks.add(task); 524 } 525 } 526 } 527 528 @VisibleForTesting 529 int getSnapshotMode(Task task) { 530 final ActivityRecord topChild = task.getTopMostActivity(); 531 if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) { 532 return SNAPSHOT_MODE_NONE; 533 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) { 534 return SNAPSHOT_MODE_APP_THEME; 535 } else { 536 return SNAPSHOT_MODE_REAL; 537 } 538 } 539 540 /** 541 * If we are not allowed to take a real screenshot, this attempts to represent the app as best 542 * as possible by using the theme's window background. 543 */ 544 private TaskSnapshot drawAppThemeSnapshot(Task task) { 545 final ActivityRecord topChild = task.getTopMostActivity(); 546 if (topChild == null) { 547 return null; 548 } 549 final WindowState mainWindow = topChild.findMainWindow(); 550 if (mainWindow == null) { 551 return null; 552 } 553 final int color = ColorUtils.setAlphaComponent( 554 task.getTaskDescription().getBackgroundColor(), 255); 555 final LayoutParams attrs = mainWindow.getAttrs(); 556 final Rect taskBounds = task.getBounds(); 557 final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride(); 558 final Rect systemBarInsets = getSystemBarInsets(taskBounds, insetsState); 559 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, 560 attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(), 561 mHighResTaskSnapshotScale, insetsState); 562 final int taskWidth = taskBounds.width(); 563 final int taskHeight = taskBounds.height(); 564 final int width = (int) (taskWidth * mHighResTaskSnapshotScale); 565 final int height = (int) (taskHeight * mHighResTaskSnapshotScale); 566 567 final RenderNode node = RenderNode.create("TaskSnapshotController", null); 568 node.setLeftTopRightBottom(0, 0, width, height); 569 node.setClipToBounds(false); 570 final RecordingCanvas c = node.start(width, height); 571 c.drawColor(color); 572 decorPainter.setInsets(systemBarInsets); 573 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); 574 node.end(c); 575 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); 576 if (hwBitmap == null) { 577 return null; 578 } 579 final Rect contentInsets = new Rect(systemBarInsets); 580 final Rect letterboxInsets = topChild.getLetterboxInsets(); 581 InsetUtils.addInsets(contentInsets, letterboxInsets); 582 583 // Note, the app theme snapshot is never translucent because we enforce a non-translucent 584 // color above 585 return new TaskSnapshot( 586 System.currentTimeMillis() /* id */, 587 topChild.mActivityComponent, hwBitmap.getHardwareBuffer(), 588 hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation, 589 mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight), 590 contentInsets, letterboxInsets, false /* isLowResolution */, 591 false /* isRealSnapshot */, task.getWindowingMode(), 592 getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */); 593 } 594 595 /** 596 * Called when an {@link ActivityRecord} has been removed. 597 */ 598 void onAppRemoved(ActivityRecord activity) { 599 mCache.onAppRemoved(activity); 600 } 601 602 /** 603 * Called when the process of an {@link ActivityRecord} has died. 604 */ 605 void onAppDied(ActivityRecord activity) { 606 mCache.onAppDied(activity); 607 } 608 609 void notifyTaskRemovedFromRecents(int taskId, int userId) { 610 mCache.onTaskRemoved(taskId); 611 mPersister.onTaskRemovedFromRecents(taskId, userId); 612 } 613 614 void removeSnapshotCache(int taskId) { 615 mCache.removeRunningEntry(taskId); 616 } 617 618 /** 619 * See {@link TaskSnapshotPersister#removeObsoleteFiles} 620 */ 621 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) { 622 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds); 623 } 624 625 /** 626 * Temporarily pauses/unpauses persisting of task snapshots. 627 * 628 * @param paused Whether task snapshot persisting should be paused. 629 */ 630 void setPersisterPaused(boolean paused) { 631 mPersister.setPaused(paused); 632 } 633 634 /** 635 * Called when screen is being turned off. 636 */ 637 void screenTurningOff(int displayId, ScreenOffListener listener) { 638 if (shouldDisableSnapshots()) { 639 listener.onScreenOff(); 640 return; 641 } 642 643 // We can't take a snapshot when screen is off, so take a snapshot now! 644 mHandler.post(() -> { 645 try { 646 synchronized (mService.mGlobalLock) { 647 snapshotForSleeping(displayId); 648 } 649 } finally { 650 listener.onScreenOff(); 651 } 652 }); 653 } 654 655 /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */ 656 void snapshotForSleeping(int displayId) { 657 if (shouldDisableSnapshots() || !mService.mDisplayEnabled) { 658 return; 659 } 660 final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId); 661 if (displayContent == null) { 662 return; 663 } 664 mTmpTasks.clear(); 665 displayContent.forAllTasks(task -> { 666 // Since RecentsAnimation will handle task snapshot while switching apps with the best 667 // capture timing (e.g. IME window capture), No need additional task capture while task 668 // is controlled by RecentsAnimation. 669 if (task.isVisible() && !task.isAnimatingByRecents()) { 670 mTmpTasks.add(task); 671 } 672 }); 673 // Allow taking snapshot of home when turning screen off to reduce the delay of waking from 674 // secure lock to home. 675 final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY 676 && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId); 677 snapshotTasks(mTmpTasks, allowSnapshotHome); 678 } 679 680 /** 681 * @return The {@link Appearance} flags for the top fullscreen opaque window in the given 682 * {@param task}. 683 */ 684 private @Appearance int getAppearance(Task task) { 685 final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity(); 686 final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null 687 ? topFullscreenActivity.getTopFullscreenOpaqueWindow() 688 : null; 689 if (topFullscreenOpaqueWindow != null) { 690 return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance; 691 } 692 return 0; 693 } 694 695 static Rect getSystemBarInsets(Rect frame, InsetsState state) { 696 return state.calculateInsets( 697 frame, Type.systemBars(), false /* ignoreVisibility */).toRect(); 698 } 699 700 void dump(PrintWriter pw, String prefix) { 701 pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale); 702 mCache.dump(pw, prefix); 703 } 704 } 705