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