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