• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.server.wm;
17 
18 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 
22 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityManager;
29 import android.content.pm.PackageManager;
30 import android.content.res.Configuration;
31 import android.graphics.Bitmap;
32 import android.graphics.PixelFormat;
33 import android.graphics.Point;
34 import android.graphics.RecordingCanvas;
35 import android.graphics.Rect;
36 import android.graphics.RenderNode;
37 import android.hardware.HardwareBuffer;
38 import android.os.SystemClock;
39 import android.os.Trace;
40 import android.util.Pair;
41 import android.util.Slog;
42 import android.view.InsetsState;
43 import android.view.SurfaceControl;
44 import android.view.ThreadedRenderer;
45 import android.view.WindowInsets;
46 import android.view.WindowManager;
47 import android.window.ScreenCapture;
48 import android.window.SnapshotDrawerUtils;
49 import android.window.TaskSnapshot;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.graphics.ColorUtils;
53 import com.android.server.wm.utils.InsetUtils;
54 import com.android.window.flags.Flags;
55 
56 import java.io.PrintWriter;
57 import java.util.function.Consumer;
58 import java.util.function.Supplier;
59 
60 /**
61  * Base class for a Snapshot controller
62  * @param <TYPE> The basic type, either Task or ActivityRecord
63  * @param <CACHE> The basic cache for either Task or ActivityRecord
64  */
65 abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
66         CACHE extends SnapshotCache<TYPE>> {
67     static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotController" : TAG_WM;
68     /**
69      * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
70      * used as the snapshot.
71      */
72     @VisibleForTesting
73     static final int SNAPSHOT_MODE_REAL = 0;
74     /**
75      * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
76      * we should try to use the app theme to create a fake representation of the app.
77      */
78     @VisibleForTesting
79     static final int SNAPSHOT_MODE_APP_THEME = 1;
80     /**
81      * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
82      */
83     @VisibleForTesting
84     static final int SNAPSHOT_MODE_NONE = 2;
85     static final float THEME_SNAPSHOT_MIN_Length = 128.0f;
86 
87     protected final WindowManagerService mService;
88     protected final float mHighResSnapshotScale;
89 
90     /**
91      * The transition change info of the target to capture screenshot. It is only non-null when
92      * capturing a snapshot with a given change info. It must be cleared after
93      * {@link #recordSnapshotInner} is done.
94      */
95     protected Transition.ChangeInfo mCurrentChangeInfo;
96 
97     /**
98      * Flag indicating whether we are running on an Android TV device.
99      */
100     protected final boolean mIsRunningOnTv;
101     /**
102      * Flag indicating whether we are running on an IoT device.
103      */
104     protected final boolean mIsRunningOnIoT;
105 
106     protected CACHE mCache;
107     /**
108      * Flag indicating if task snapshot is enabled on this device.
109      */
110     private boolean mSnapshotEnabled;
111 
AbsAppSnapshotController(WindowManagerService service)112     AbsAppSnapshotController(WindowManagerService service) {
113         mService = service;
114         mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
115                 PackageManager.FEATURE_LEANBACK);
116         mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
117                 PackageManager.FEATURE_EMBEDDED);
118         mHighResSnapshotScale = initSnapshotScale();
119     }
120 
initSnapshotScale()121     protected float initSnapshotScale() {
122         final float config = mService.mContext.getResources().getFloat(
123                 com.android.internal.R.dimen.config_highResTaskSnapshotScale);
124         return Math.max(Math.min(config, 1f), 0.1f);
125     }
126 
127     /**
128      * Set basic cache to the controller.
129      */
initialize(CACHE cache)130     protected void initialize(CACHE cache) {
131         mCache = cache;
132     }
133 
setSnapshotReleaser(Consumer<HardwareBuffer> releaser)134     void setSnapshotReleaser(Consumer<HardwareBuffer> releaser) {
135         mCache.setSafeSnapshotReleaser(releaser);
136     }
137 
setSnapshotEnabled(boolean enabled)138     void setSnapshotEnabled(boolean enabled) {
139         mSnapshotEnabled = enabled;
140     }
141 
shouldDisableSnapshots()142     boolean shouldDisableSnapshots() {
143         return mIsRunningOnTv || mIsRunningOnIoT || !mSnapshotEnabled;
144     }
145 
getTopActivity(TYPE source)146     abstract ActivityRecord getTopActivity(TYPE source);
getTaskDescription(TYPE source)147     abstract ActivityManager.TaskDescription getTaskDescription(TYPE source);
148     /**
149      * Find the window for a given task to take a snapshot. Top child of the task is usually the one
150      * we're looking for, but during app transitions, trampoline activities can appear in the
151      * children, which should be ignored.
152      */
153     @Nullable
findAppTokenForSnapshot(TYPE source)154     protected abstract ActivityRecord findAppTokenForSnapshot(TYPE source);
use16BitFormat()155     protected abstract boolean use16BitFormat();
getLetterboxInsets(ActivityRecord topActivity)156     protected abstract Rect getLetterboxInsets(ActivityRecord topActivity);
157 
158     /**
159      * This is different than {@link #recordSnapshotInner(TYPE, boolean, Consumer)}  because it
160      * doesn't store the snapshot to the cache and returns the TaskSnapshot immediately.
161      */
162     @VisibleForTesting
captureSnapshot(TYPE source, boolean allowAppTheme)163     SnapshotSupplier captureSnapshot(TYPE source, boolean allowAppTheme) {
164         final SnapshotSupplier supplier = new SnapshotSupplier();
165         switch (getSnapshotMode(source)) {
166             case SNAPSHOT_MODE_APP_THEME:
167                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot");
168                 if (Flags.excludeDrawingAppThemeSnapshotFromLock()) {
169                     if (allowAppTheme) {
170                         supplier.setSupplier(drawAppThemeSnapshot(source));
171                     }
172                 } else {
173                     final Supplier<TaskSnapshot> original = drawAppThemeSnapshot(source);
174                     final TaskSnapshot snapshot = original != null ? original.get() : null;
175                     supplier.setSnapshot(snapshot);
176                 }
177                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
178                 break;
179             case SNAPSHOT_MODE_REAL:
180                 supplier.setSnapshot(snapshot(source));
181                 break;
182             default:
183                 break;
184         }
185         return supplier;
186     }
187 
188     /**
189      * @param allowAppTheme If true, allows to draw app theme snapshot when it's not allowed to take
190      *                      a real screenshot, but create a fake representation of the app.
191      * @param inLockConsumer Extra task to do in WM lock when first get the snapshot object.
192      */
recordSnapshotInner(TYPE source, boolean allowAppTheme, @Nullable Consumer<TaskSnapshot> inLockConsumer)193     final SnapshotSupplier recordSnapshotInner(TYPE source, boolean allowAppTheme,
194             @Nullable Consumer<TaskSnapshot> inLockConsumer) {
195         if (shouldDisableSnapshots()) {
196             return null;
197         }
198         final SnapshotSupplier supplier = captureSnapshot(source, allowAppTheme);
199         supplier.setConsumer(t -> {
200             synchronized (mService.mGlobalLock) {
201                 if (!source.isAttached()) {
202                     return;
203                 }
204                 mCache.putSnapshot(source, t);
205                 if (inLockConsumer != null) {
206                     inLockConsumer.accept(t);
207                 }
208             }
209         });
210         return supplier;
211     }
212 
getSnapshotMode(TYPE source)213     int getSnapshotMode(TYPE source) {
214         final int type = source.getActivityType();
215         if (type == ACTIVITY_TYPE_RECENTS || type == ACTIVITY_TYPE_DREAM) {
216             return SNAPSHOT_MODE_NONE;
217         }
218         if (type == ACTIVITY_TYPE_HOME) {
219             return SNAPSHOT_MODE_REAL;
220         }
221         final ActivityRecord topChild = getTopActivity(source);
222         if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
223             return SNAPSHOT_MODE_APP_THEME;
224         }
225         return SNAPSHOT_MODE_REAL;
226     }
227 
228     @Nullable
snapshot(TYPE source)229     TaskSnapshot snapshot(TYPE source) {
230         return snapshot(source, mHighResSnapshotScale);
231     }
232 
233     @Nullable
snapshot(TYPE source, float scale)234     TaskSnapshot snapshot(TYPE source, float scale) {
235         TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
236         final Rect crop = prepareTaskSnapshot(source, builder);
237         if (crop == null) {
238             // Failed some pre-req. Has been logged.
239             return null;
240         }
241         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createSnapshot");
242         final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = createSnapshot(source,
243                 scale, crop, builder);
244         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
245         if (screenshotBuffer == null) {
246             // Failed to acquire image. Has been logged.
247             return null;
248         }
249         builder.setCaptureTime(SystemClock.elapsedRealtimeNanos());
250         builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
251         builder.setColorSpace(screenshotBuffer.getColorSpace());
252         final TaskSnapshot snapshot = builder.build();
253         return validateSnapshot(snapshot);
254     }
255 
validateSnapshot(@onNull TaskSnapshot snapshot)256     private static TaskSnapshot validateSnapshot(@NonNull TaskSnapshot snapshot) {
257         final HardwareBuffer buffer = snapshot.getHardwareBuffer();
258         if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
259             buffer.close();
260             Slog.e(TAG, "Invalid snapshot dimensions " + buffer.getWidth() + "x"
261                     + buffer.getHeight());
262             return null;
263         }
264         return snapshot;
265     }
266 
267     @Nullable
createSnapshot(@onNull TYPE source, float scaleFraction, Rect crop, TaskSnapshot.Builder builder)268     ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
269             float scaleFraction, Rect crop, TaskSnapshot.Builder builder) {
270         if (source.getSurfaceControl() == null) {
271             if (DEBUG_SCREENSHOT) {
272                 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + source);
273             }
274             return null;
275         }
276         SurfaceControl[] excludeLayers;
277         final WindowState imeWindow = source.getDisplayContent().mInputMethodWindow;
278         // Exclude IME window snapshot when IME isn't proper to attach to app.
279         final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
280                 && !source.getDisplayContent().shouldImeAttachedToApp();
281         final WindowState navWindow =
282                 source.getDisplayContent().getDisplayPolicy().getNavigationBar();
283         // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
284         // the swiped app when entering recent app, therefore the task will contain the navigation
285         // bar and we should exclude it from snapshot.
286         final boolean excludeNavBar = navWindow != null;
287         if (excludeIme && excludeNavBar) {
288             excludeLayers = new SurfaceControl[2];
289             excludeLayers[0] = imeWindow.getSurfaceControl();
290             excludeLayers[1] = navWindow.getSurfaceControl();
291         } else if (excludeIme || excludeNavBar) {
292             excludeLayers = new SurfaceControl[1];
293             excludeLayers[0] =
294                     excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
295         } else {
296             excludeLayers = new SurfaceControl[0];
297         }
298         builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
299         final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
300                 ScreenCapture.captureLayersExcluding(
301                         source.getSurfaceControl(), crop, scaleFraction,
302                         builder.getPixelFormat(), excludeLayers);
303         final HardwareBuffer buffer = screenshotBuffer == null ? null
304                 : screenshotBuffer.getHardwareBuffer();
305         if (isInvalidHardwareBuffer(buffer)) {
306             return null;
307         }
308         return screenshotBuffer;
309     }
310 
isInvalidHardwareBuffer(HardwareBuffer buffer)311     static boolean isInvalidHardwareBuffer(HardwareBuffer buffer) {
312         return buffer == null || buffer.isClosed() // This must be checked before getting size.
313                 || buffer.getWidth() <= 1 || buffer.getHeight() <= 1;
314     }
315 
316     /**
317      * Validates the state of the Task is appropriate to capture a snapshot, collects
318      * information from the task and populates the builder.
319      *
320      * @param source the window to capture
321      * @param builder the snapshot builder to populate
322      *
323      * @return true if the state of the task is ok to proceed
324      */
325     @VisibleForTesting
326     @Nullable
prepareTaskSnapshot(TYPE source, TaskSnapshot.Builder builder)327     Rect prepareTaskSnapshot(TYPE source, TaskSnapshot.Builder builder) {
328         final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(source);
329         if (result == null) {
330             return null;
331         }
332         final ActivityRecord activity = result.first;
333         final WindowState mainWindow = result.second;
334         final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
335                 mainWindow.getInsetsStateWithVisibilityOverride());
336         final Rect letterboxInsets = getLetterboxInsets(activity);
337         InsetUtils.addInsets(contentInsets, letterboxInsets);
338         builder.setIsRealSnapshot(true);
339         builder.setId(System.currentTimeMillis());
340         builder.setContentInsets(contentInsets);
341         builder.setLetterboxInsets(letterboxInsets);
342         final boolean isWindowTranslucent = mainWindow.mAttrs.format != PixelFormat.OPAQUE;
343         final boolean isShowWallpaper = mainWindow.hasWallpaper();
344         int pixelFormat = builder.getPixelFormat();
345         if (pixelFormat == PixelFormat.UNKNOWN) {
346             pixelFormat = use16BitFormat() && activity.fillsParent()
347                     && !(isWindowTranslucent && isShowWallpaper)
348                     ? PixelFormat.RGB_565
349                     : PixelFormat.RGBA_8888;
350         }
351         final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
352                 && (!activity.fillsParent() || isWindowTranslucent);
353         builder.setTopActivityComponent(activity.mActivityComponent);
354         builder.setPixelFormat(pixelFormat);
355         builder.setIsTranslucent(isTranslucent);
356         builder.setWindowingMode(source.getWindowingMode());
357         builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
358         builder.setUiMode(activity.getConfiguration().uiMode);
359 
360         final Configuration taskConfig = activity.getTask().getConfiguration();
361         final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
362         final Rect outCrop = new Rect();
363         final Point taskSize = new Point();
364         final Transition.ChangeInfo changeInfo = mCurrentChangeInfo;
365         if (changeInfo != null && changeInfo.mRotation != displayRotation) {
366             // For example, the source is closing and display rotation changes at the same time.
367             // The snapshot should record the state in previous rotation.
368             outCrop.set(changeInfo.mAbsoluteBounds);
369             taskSize.set(changeInfo.mAbsoluteBounds.right, changeInfo.mAbsoluteBounds.bottom);
370             builder.setRotation(changeInfo.mRotation);
371             builder.setOrientation(changeInfo.mAbsoluteBounds.height()
372                     >= changeInfo.mAbsoluteBounds.width()
373                     ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE);
374         } else {
375             final Configuration srcConfig = source.getConfiguration();
376             outCrop.set(srcConfig.windowConfiguration.getBounds());
377             final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
378             taskSize.set(taskBounds.width(), taskBounds.height());
379             builder.setRotation(displayRotation);
380             builder.setOrientation(srcConfig.orientation);
381         }
382         outCrop.offsetTo(0, 0);
383         builder.setTaskSize(taskSize);
384         return outCrop;
385     }
386 
387     /**
388      * Check if the state of the Task is appropriate to capture a snapshot, such like the task
389      * snapshot or the associated IME surface snapshot.
390      *
391      * @param source the target object to capture the snapshot
392      * @return Pair of (the top activity of the task, the main window of the task) if passed the
393      * state checking. Returns {@code null} if the task state isn't ready to snapshot.
394      */
checkIfReadyToSnapshot(TYPE source)395     Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(TYPE source) {
396         if (!mService.mPolicy.isScreenOn()) {
397             if (DEBUG_SCREENSHOT) {
398                 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
399             }
400             return null;
401         }
402         final ActivityRecord activity = findAppTokenForSnapshot(source);
403         if (activity == null) {
404             if (DEBUG_SCREENSHOT) {
405                 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + source);
406             }
407             return null;
408         }
409         final WindowState mainWindow = activity.findMainWindow();
410         if (mainWindow == null) {
411             Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + source);
412             return null;
413         }
414         if (activity.hasFixedRotationTransform()) {
415             if (DEBUG_SCREENSHOT) {
416                 Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity);
417             }
418             // The activity is in a temporal state that it has different rotation than the task.
419             return null;
420         }
421         return new Pair<>(activity, mainWindow);
422     }
423 
424     /**
425      * If we are not allowed to take a real screenshot, this attempts to represent the app as best
426      * as possible by using the theme's window background.
427      */
drawAppThemeSnapshot(TYPE source)428     private Supplier<TaskSnapshot> drawAppThemeSnapshot(TYPE source) {
429         final ActivityRecord topActivity = getTopActivity(source);
430         if (topActivity == null) {
431             return null;
432         }
433         final WindowState mainWindow = topActivity.findMainWindow();
434         if (mainWindow == null) {
435             return null;
436         }
437         final ActivityManager.TaskDescription taskDescription = getTaskDescription(source);
438         final int color = ColorUtils.setAlphaComponent(
439                 taskDescription.getBackgroundColor(), 255);
440         final WindowManager.LayoutParams attrs = mainWindow.mAttrs;
441         final Rect taskBounds = source.getBounds();
442         final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
443         final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
444         final int taskWidth = taskBounds.width();
445         final int taskHeight = taskBounds.height();
446         float scale = mHighResSnapshotScale;
447         if (Flags.reduceTaskSnapshotMemoryUsage()) {
448             final int minLength = Math.min(taskWidth, taskHeight);
449             if (THEME_SNAPSHOT_MIN_Length < minLength) {
450                 scale = Math.min(THEME_SNAPSHOT_MIN_Length / minLength, scale);
451             }
452         }
453         final SnapshotDrawerUtils.SystemBarBackgroundPainter
454                 decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
455                 attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription,
456                 scale, mainWindow.getRequestedVisibleTypes());
457         final int width = (int) (taskWidth * scale);
458         final int height = (int) (taskHeight * scale);
459         final RenderNode node = RenderNode.create("SnapshotController", null);
460         node.setLeftTopRightBottom(0, 0, width, height);
461         node.setClipToBounds(false);
462         final RecordingCanvas c = node.start(width, height);
463         c.drawColor(color);
464         decorPainter.setInsets(systemBarInsets);
465         decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawFrame */);
466         node.end(c);
467 
468         final Rect contentInsets = new Rect(systemBarInsets);
469         final Rect letterboxInsets = getLetterboxInsets(topActivity);
470         InsetUtils.addInsets(contentInsets, letterboxInsets);
471 
472         final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
473         builder.setIsRealSnapshot(false);
474         builder.setId(System.currentTimeMillis());
475         builder.setContentInsets(contentInsets);
476         builder.setLetterboxInsets(letterboxInsets);
477 
478         builder.setTopActivityComponent(topActivity.mActivityComponent);
479         // Note, the app theme snapshot is never translucent because we enforce a
480         // non-translucent color above.
481         builder.setIsTranslucent(false);
482         builder.setWindowingMode(source.getWindowingMode());
483         builder.setAppearance(attrs.insetsFlags.appearance);
484         builder.setUiMode(topActivity.getConfiguration().uiMode);
485 
486         builder.setRotation(mainWindow.getWindowConfiguration().getRotation());
487         builder.setOrientation(mainWindow.getConfiguration().orientation);
488         builder.setTaskSize(new Point(taskWidth, taskHeight));
489         builder.setCaptureTime(SystemClock.elapsedRealtimeNanos());
490 
491         return () -> {
492             try {
493                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot_acquire");
494                 // Do not hold WM lock when calling to render thread.
495                 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width,
496                         height);
497                 if (hwBitmap == null) {
498                     return null;
499                 }
500                 builder.setSnapshot(hwBitmap.getHardwareBuffer());
501                 builder.setColorSpace(hwBitmap.getColorSpace());
502                 return validateSnapshot(builder.build());
503             } finally {
504                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
505             }
506         };
507     }
508 
509     static Rect getSystemBarInsets(Rect frame, InsetsState state) {
510         return state.calculateInsets(
511                 frame, WindowInsets.Type.systemBars(), false /* ignoreVisibility */).toRect();
512     }
513 
514     /**
515      * Called when an {@link ActivityRecord} has been removed.
516      */
517     void onAppRemoved(ActivityRecord activity) {
518         mCache.onAppRemoved(activity);
519     }
520 
521     /**
522      * Called when the process of an {@link ActivityRecord} has died.
523      */
524     void onAppDied(ActivityRecord activity) {
525         mCache.onAppDied(activity);
526     }
527 
528     boolean isAnimatingByRecents(@NonNull Task task) {
529         return task.isAnimatingByRecents();
530     }
531 
532     void dump(PrintWriter pw, String prefix) {
533         pw.println(prefix + "mHighResSnapshotScale=" + mHighResSnapshotScale);
534         pw.println(prefix + "mSnapshotEnabled=" + mSnapshotEnabled);
535         mCache.dump(pw, prefix);
536     }
537 
538     static class SnapshotSupplier implements Supplier<TaskSnapshot> {
539 
540         private TaskSnapshot mSnapshot;
541         private boolean mHasSet;
542         private Consumer<TaskSnapshot> mConsumer;
543         private Supplier<TaskSnapshot> mSupplier;
544 
545         /** Callback when the snapshot is get for the first time. */
546         void setConsumer(@NonNull Consumer<TaskSnapshot> consumer) {
547             mConsumer = consumer;
548         }
549 
550         void setSupplier(@NonNull Supplier<TaskSnapshot> createSupplier) {
551             mSupplier = createSupplier;
552         }
553 
554         void setSnapshot(TaskSnapshot snapshot) {
555             mSnapshot = snapshot;
556         }
557 
558         void handleSnapshot() {
559             if (mHasSet) {
560                 return;
561             }
562             mHasSet = true;
563             if (mSnapshot == null) {
564                 mSnapshot = mSupplier != null ? mSupplier.get() : null;
565             }
566             if (mConsumer != null && mSnapshot != null) {
567                 mConsumer.accept(mSnapshot);
568             }
569         }
570 
571         @Override
572         @Nullable
573         public TaskSnapshot get() {
574             handleSnapshot();
575             return mSnapshot;
576         }
577     }
578 }
579