blob: a7a2b534131d6fccceafd101cb9dcc4d9b46c946 [file] [log] [blame]
Jorim Jaggi02886a82016-12-06 09:10:06 -08001/*
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
17package com.android.server.wm;
18
Matthew Ngcb7ac672017-07-21 17:27:42 -070019import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
20import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
chaviwfbe47df2017-11-10 16:14:49 -080021import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
Winson Chung3e13ef82017-06-29 12:41:14 -070022import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24
Jorim Jaggi02886a82016-12-06 09:10:06 -080025import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010026import android.app.ActivityManager;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010027import android.app.ActivityManager.TaskSnapshot;
Keyvan Amirie681cec2017-06-05 22:48:26 -070028import android.content.pm.PackageManager;
Jorim Jaggi6aead1c2017-05-23 15:07:44 +020029import android.graphics.Bitmap;
Winson Chung51f42d22018-02-01 14:59:38 -080030import android.graphics.Color;
Jorim Jaggi02886a82016-12-06 09:10:06 -080031import android.graphics.GraphicBuffer;
Jorim Jaggi30d64f32017-04-07 16:33:17 +020032import android.graphics.Rect;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010033import android.os.Environment;
Jorim Jaggi51304d72017-05-17 17:25:32 +020034import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080035import android.util.ArraySet;
Winson Chung3e13ef82017-06-29 12:41:14 -070036import android.util.Slog;
Winson Chung91092762017-05-24 14:08:48 -070037import android.view.DisplayListCanvas;
38import android.view.RenderNode;
chaviwfbe47df2017-11-10 16:14:49 -080039import android.view.SurfaceControl;
Winson Chung91092762017-05-24 14:08:48 -070040import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020041import android.view.WindowManager.LayoutParams;
Guang Zhu09486a32017-06-06 03:27:44 +000042
Jorim Jaggi02886a82016-12-06 09:10:06 -080043import com.android.internal.annotations.VisibleForTesting;
Winson Chung51f42d22018-02-01 14:59:38 -080044import com.android.internal.graphics.ColorUtils;
Adrian Roose99bc052017-11-20 17:55:31 +010045import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
46import com.android.server.policy.WindowManagerPolicy.StartingSurface;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020047import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080048
Adrian Roose99bc052017-11-20 17:55:31 +010049import com.google.android.collect.Sets;
50
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010051import java.io.PrintWriter;
52
Jorim Jaggi02886a82016-12-06 09:10:06 -080053/**
54 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
55 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
56 * like without any copying.
57 * <p>
58 * System applications may retrieve a snapshot to represent the current state of a task, and draw
59 * them in their own process.
60 * <p>
61 * When we task becomes visible again, we show a starting window with the snapshot as the content to
62 * make app transitions more responsive.
63 * <p>
64 * To access this class, acquire the global window manager lock.
65 */
66class TaskSnapshotController {
Winson Chung3e13ef82017-06-29 12:41:14 -070067 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
Jorim Jaggi02886a82016-12-06 09:10:06 -080068
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010069 /**
70 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
71 * used as the snapshot.
72 */
73 @VisibleForTesting
74 static final int SNAPSHOT_MODE_REAL = 0;
75
76 /**
77 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
78 * we should try to use the app theme to create a dummy representation of the app.
79 */
80 @VisibleForTesting
81 static final int SNAPSHOT_MODE_APP_THEME = 1;
82
83 /**
84 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
85 */
86 @VisibleForTesting
87 static final int SNAPSHOT_MODE_NONE = 2;
88
Jorim Jaggi02886a82016-12-06 09:10:06 -080089 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080090
Jorim Jaggi7361bab2017-01-16 17:17:58 +010091 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010092 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
93 Environment::getDataSystemCeDirectory);
94 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080095 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020096 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080097
chaviw23ee71c2017-12-18 11:29:41 -080098 private final Rect mTmpRect = new Rect();
99
Keyvan Amirie681cec2017-06-05 22:48:26 -0700100 /**
101 * Flag indicating whether we are running on an Android TV device.
102 */
103 private final boolean mIsRunningOnTv;
104
Juho Haf5dd7cc2017-06-15 11:38:56 +0900105 /**
106 * Flag indicating whether we are running on an IoT device.
107 */
108 private final boolean mIsRunningOnIoT;
109
Matthew Ng95378192017-08-16 11:57:00 -0700110 /**
111 * Flag indicating whether we are running on an Android Wear device.
112 */
113 private final boolean mIsRunningOnWear;
114
Jorim Jaggi02886a82016-12-06 09:10:06 -0800115 TaskSnapshotController(WindowManagerService service) {
116 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100117 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700118 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
119 PackageManager.FEATURE_LEANBACK);
Juho Haf5dd7cc2017-06-15 11:38:56 +0900120 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
121 PackageManager.FEATURE_EMBEDDED);
Matthew Ng95378192017-08-16 11:57:00 -0700122 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
123 PackageManager.FEATURE_WATCH);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800124 }
125
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100126 void systemReady() {
127 mPersister.start();
128 }
129
Jorim Jaggi02886a82016-12-06 09:10:06 -0800130 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100131 handleClosingApps(mService.mClosingApps);
132 }
133
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100134 /**
135 * Called when the visibility of an app changes outside of the regular app transition flow.
136 */
137 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100138 if (!visible) {
139 handleClosingApps(Sets.newArraySet(appWindowToken));
140 }
141 }
142
143 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700144 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100145 return;
146 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800147
148 // We need to take a snapshot of the task if and only if all activities of the task are
149 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100150 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200151 snapshotTasks(mTmpTasks);
152
153 }
154
155 private void snapshotTasks(ArraySet<Task> tasks) {
156 for (int i = tasks.size() - 1; i >= 0; i--) {
157 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100158 final int mode = getSnapshotMode(task);
159 final TaskSnapshot snapshot;
160 switch (mode) {
161 case SNAPSHOT_MODE_NONE:
162 continue;
163 case SNAPSHOT_MODE_APP_THEME:
164 snapshot = drawAppThemeSnapshot(task);
165 break;
166 case SNAPSHOT_MODE_REAL:
167 snapshot = snapshotTask(task);
168 break;
169 default:
170 snapshot = null;
171 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800172 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100173 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700174 final GraphicBuffer buffer = snapshot.getSnapshot();
175 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
176 buffer.destroy();
177 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
178 + buffer.getHeight());
179 } else {
180 mCache.putSnapshot(task, snapshot);
181 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
182 if (task.getController() != null) {
183 task.getController().reportSnapshotChanged(snapshot);
184 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100185 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800186 }
187 }
188 }
189
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100190 /**
191 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
192 * MANAGER LOCK WHEN CALLING THIS METHOD!
193 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100194 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
195 boolean reducedResolution) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700196 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
197 || DISABLE_FULL_SIZED_BITMAPS);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800198 }
199
200 /**
201 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
202 * MANAGER LOCK WHEN CALLING THIS METHOD!
203 */
204 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200205 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800206 return TaskSnapshotSurface.create(mService, token, snapshot);
207 }
208
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100209 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100210 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800211 if (top == null) {
212 return null;
213 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700214 final WindowState mainWindow = top.findMainWindow();
215 if (mainWindow == null) {
216 return null;
217 }
chaviwfbe47df2017-11-10 16:14:49 -0800218 if (!mService.mPolicy.isScreenOn()) {
219 if (DEBUG_SCREENSHOT) {
220 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
221 }
222 return null;
223 }
224 if (task.getSurfaceControl() == null) {
225 return null;
226 }
227
chaviw7f1fa992018-01-10 13:52:12 -0800228 if (top.hasCommittedReparentToAnimationLeash()) {
229 if (DEBUG_SCREENSHOT) {
230 Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + top);
231 }
232 return null;
233 }
234
235 final boolean hasVisibleChild = top.forAllWindows(
236 // Ensure at least one window for the top app is visible before attempting to take
237 // a screenshot. Visible here means that the WSA surface is shown and has an alpha
238 // greater than 0.
239 ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown()
240 && ws.mWinAnimator.mLastAlpha > 0f, true);
241
242 if (!hasVisibleChild) {
243 if (DEBUG_SCREENSHOT) {
244 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
245 }
246 return null;
247 }
248
Matthew Ngcb7ac672017-07-21 17:27:42 -0700249 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
250 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
chaviw23ee71c2017-12-18 11:29:41 -0800251 task.getBounds(mTmpRect);
252 mTmpRect.offsetTo(0, 0);
chaviwfbe47df2017-11-10 16:14:49 -0800253
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000254 final GraphicBuffer buffer = SurfaceControl.captureLayers(
chaviw23ee71c2017-12-18 11:29:41 -0800255 task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
chaviwfbe47df2017-11-10 16:14:49 -0800256
Juho Had864b442017-06-12 20:15:31 +0900257 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
chaviwfbe47df2017-11-10 16:14:49 -0800258 if (DEBUG_SCREENSHOT) {
chaviw7f1fa992018-01-10 13:52:12 -0800259 Slog.w(TAG_WM, "Failed to take screenshot for " + task);
chaviwfbe47df2017-11-10 16:14:49 -0800260 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800261 return null;
262 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100263 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Adrian Roos98a146d2017-11-29 16:39:44 +0100264 getInsetsFromTaskBounds(mainWindow, task),
Matthew Ngcb7ac672017-07-21 17:27:42 -0700265 isLowRamDevice /* reduced */, scaleFraction /* scale */);
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200266 }
267
Keyvan Amirie681cec2017-06-05 22:48:26 -0700268 private boolean shouldDisableSnapshots() {
Jorim Jaggie7d2b852017-08-28 17:55:15 +0200269 return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
Keyvan Amirie681cec2017-06-05 22:48:26 -0700270 }
271
Adrian Roos98a146d2017-11-29 16:39:44 +0100272 private Rect getInsetsFromTaskBounds(WindowState state, Task task) {
273 final Rect r = new Rect();
274 r.set(state.getContentFrameLw());
275 r.intersectUnchecked(state.getStableFrameLw());
276
277 final Rect taskBounds = task.getBounds();
278
279 r.set(Math.max(0, r.left - taskBounds.left),
280 Math.max(0, r.top - taskBounds.top),
281 Math.max(0, taskBounds.right - r.right),
282 Math.max(0, taskBounds.bottom - r.bottom));
283 return r;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800284 }
285
286 /**
287 * Retrieves all closing tasks based on the list of closing apps during an app transition.
288 */
289 @VisibleForTesting
290 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
291 outClosingTasks.clear();
292 for (int i = closingApps.size() - 1; i >= 0; i--) {
293 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800294 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800295
296 // If the task of the app is not visible anymore, it means no other app in that task
297 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800298 if (task != null && !task.isVisible()) {
299 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800300 }
301 }
302 }
303
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100304 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100305 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100306 final AppWindowToken topChild = task.getTopChild();
Wale Ogunwale68278562017-09-23 17:13:55 -0700307 if (!task.isActivityTypeStandardOrUndefined()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100308 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200309 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100310 return SNAPSHOT_MODE_APP_THEME;
311 } else {
312 return SNAPSHOT_MODE_REAL;
313 }
314 }
315
316 /**
317 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
318 * as possible by using the theme's window background.
319 */
320 private TaskSnapshot drawAppThemeSnapshot(Task task) {
321 final AppWindowToken topChild = task.getTopChild();
322 if (topChild == null) {
323 return null;
324 }
325 final WindowState mainWindow = topChild.findMainWindow();
326 if (mainWindow == null) {
327 return null;
328 }
Winson Chung51f42d22018-02-01 14:59:38 -0800329 final int color = ColorUtils.setAlphaComponent(
330 task.getTaskDescription().getBackgroundColor(), 255);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200331 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
332 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200333 final LayoutParams attrs = mainWindow.getAttrs();
334 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
335 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700336 final int width = mainWindow.getFrameLw().width();
337 final int height = mainWindow.getFrameLw().height();
338
339 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
340 node.setLeftTopRightBottom(0, 0, width, height);
341 node.setClipToBounds(false);
342 final DisplayListCanvas c = node.start(width, height);
343 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200344 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
345 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700346 node.end(c);
347 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Winson Chung6267f992017-10-06 14:01:10 -0700348 if (hwBitmap == null) {
349 return null;
350 }
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200351 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
352 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
Matthew Ng30a7f7c2017-09-18 16:29:46 -0700353 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800354 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100355
356 /**
357 * Called when an {@link AppWindowToken} has been removed.
358 */
359 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100360 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100361 }
362
363 /**
364 * Called when the process of an {@link AppWindowToken} has died.
365 */
366 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100367 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100368 }
369
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100370 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100371 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100372 mPersister.onTaskRemovedFromRecents(taskId, userId);
373 }
374
375 /**
376 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
377 */
378 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
379 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
380 }
381
Jorim Jaggia41b7292017-05-11 23:50:34 +0200382 /**
383 * Temporarily pauses/unpauses persisting of task snapshots.
384 *
385 * @param paused Whether task snapshot persisting should be paused.
386 */
387 void setPersisterPaused(boolean paused) {
388 mPersister.setPaused(paused);
389 }
390
Jorim Jaggi51304d72017-05-17 17:25:32 +0200391 /**
392 * Called when screen is being turned off.
393 */
394 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700395 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200396 listener.onScreenOff();
397 return;
398 }
399
400 // We can't take a snapshot when screen is off, so take a snapshot now!
401 mHandler.post(() -> {
402 try {
403 synchronized (mService.mWindowMap) {
404 mTmpTasks.clear();
405 mService.mRoot.forAllTasks(task -> {
406 if (task.isVisible()) {
407 mTmpTasks.add(task);
408 }
409 });
410 snapshotTasks(mTmpTasks);
411 }
412 } finally {
413 listener.onScreenOff();
414 }
415 });
416 }
417
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100418 void dump(PrintWriter pw, String prefix) {
419 mCache.dump(pw, prefix);
420 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800421}