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