• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 InsetsState insetsState = new InsetsState(mainWindow.getInsetsState());
484         mergeInsetsSources(insetsState, mainWindow.getRequestedInsetsState());
485         final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrameLw(), insetsState);
486         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
487                 attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
488                 mHighResTaskSnapshotScale, insetsState);
489         final int taskWidth = task.getBounds().width();
490         final int taskHeight = task.getBounds().height();
491         final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
492         final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
493 
494         final RenderNode node = RenderNode.create("TaskSnapshotController", null);
495         node.setLeftTopRightBottom(0, 0, width, height);
496         node.setClipToBounds(false);
497         final RecordingCanvas c = node.start(width, height);
498         c.drawColor(color);
499         decorPainter.setInsets(systemBarInsets);
500         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
501         node.end(c);
502         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
503         if (hwBitmap == null) {
504             return null;
505         }
506 
507         // Note, the app theme snapshot is never translucent because we enforce a non-translucent
508         // color above
509         return new TaskSnapshot(
510                 System.currentTimeMillis() /* id */,
511                 topChild.mActivityComponent, hwBitmap.createGraphicBufferHandle(),
512                 hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
513                 mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
514                 getInsets(mainWindow), false /* isLowResolution */,
515                 false /* isRealSnapshot */, task.getWindowingMode(),
516                 getSystemUiVisibility(task), false);
517     }
518 
519     /**
520      * Called when an {@link ActivityRecord} has been removed.
521      */
522     void onAppRemoved(ActivityRecord activity) {
523         mCache.onAppRemoved(activity);
524     }
525 
526     /**
527      * Called when the process of an {@link ActivityRecord} has died.
528      */
529     void onAppDied(ActivityRecord activity) {
530         mCache.onAppDied(activity);
531     }
532 
533     void notifyTaskRemovedFromRecents(int taskId, int userId) {
534         mCache.onTaskRemoved(taskId);
535         mPersister.onTaskRemovedFromRecents(taskId, userId);
536     }
537 
538     void removeSnapshotCache(int taskId) {
539         mCache.removeRunningEntry(taskId);
540     }
541 
542     /**
543      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
544      */
545     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
546         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
547     }
548 
549     /**
550      * Temporarily pauses/unpauses persisting of task snapshots.
551      *
552      * @param paused Whether task snapshot persisting should be paused.
553      */
554     void setPersisterPaused(boolean paused) {
555         mPersister.setPaused(paused);
556     }
557 
558     /**
559      * Called when screen is being turned off.
560      */
561     void screenTurningOff(ScreenOffListener listener) {
562         if (shouldDisableSnapshots()) {
563             listener.onScreenOff();
564             return;
565         }
566 
567         // We can't take a snapshot when screen is off, so take a snapshot now!
568         mHandler.post(() -> {
569             try {
570                 synchronized (mService.mGlobalLock) {
571                     mTmpTasks.clear();
572                     mService.mRoot.forAllTasks(task -> {
573                         if (task.isVisible()) {
574                             mTmpTasks.add(task);
575                         }
576                     });
577                     // Allow taking snapshot of home when turning screen off to reduce the delay of
578                     // waking from secure lock to home.
579                     final boolean allowSnapshotHome =
580                             mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
581                     snapshotTasks(mTmpTasks, allowSnapshotHome);
582                 }
583             } finally {
584                 listener.onScreenOff();
585             }
586         });
587     }
588 
589     /**
590      * @return The SystemUI visibility flags for the top fullscreen opaque window in the given
591      *         {@param task}.
592      */
593     private int getSystemUiVisibility(Task task) {
594         final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity();
595         final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
596                 ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
597                 : null;
598         if (topFullscreenOpaqueWindow != null) {
599             return topFullscreenOpaqueWindow.getSystemUiVisibility();
600         }
601         return 0;
602     }
603 
604     static void mergeInsetsSources(InsetsState base, InsetsState other) {
605         for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
606             final InsetsSource source = other.peekSource(type);
607             if (source != null) {
608                 base.addSource(source);
609             }
610         }
611     }
612 
613     static Rect getSystemBarInsets(Rect frame, InsetsState state) {
614         return state.calculateInsets(frame, null /* ignoringVisibilityState */,
615                 false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
616                 null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacyWindowFlags */,
617                 0 /* legacySystemUiFlags */, null /* typeSideMap */).getInsets(
618                         WindowInsets.Type.systemBars()).toRect();
619     }
620 
621     void dump(PrintWriter pw, String prefix) {
622         pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale);
623         mCache.dump(pw, prefix);
624     }
625 }
626