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