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