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