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