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