blob: a7cb3ed40347be6ad009b0f6e716f0767d09b9be [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;
Jorim Jaggi02886a82016-12-06 09:10:06 -080030import android.graphics.GraphicBuffer;
Jorim Jaggi30d64f32017-04-07 16:33:17 +020031import android.graphics.Rect;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010032import android.os.Environment;
Jorim Jaggi51304d72017-05-17 17:25:32 +020033import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080034import android.util.ArraySet;
Winson Chung3e13ef82017-06-29 12:41:14 -070035import android.util.Slog;
Winson Chung91092762017-05-24 14:08:48 -070036import android.view.DisplayListCanvas;
37import android.view.RenderNode;
chaviwfbe47df2017-11-10 16:14:49 -080038import android.view.SurfaceControl;
Winson Chung91092762017-05-24 14:08:48 -070039import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020040import android.view.WindowManager.LayoutParams;
Guang Zhu09486a32017-06-06 03:27:44 +000041
Jorim Jaggi02886a82016-12-06 09:10:06 -080042import com.android.internal.annotations.VisibleForTesting;
Adrian Roose99bc052017-11-20 17:55:31 +010043import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
44import com.android.server.policy.WindowManagerPolicy.StartingSurface;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020045import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080046
Adrian Roose99bc052017-11-20 17:55:31 +010047import com.google.android.collect.Sets;
48
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010049import java.io.PrintWriter;
50
Jorim Jaggi02886a82016-12-06 09:10:06 -080051/**
52 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
53 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
54 * like without any copying.
55 * <p>
56 * System applications may retrieve a snapshot to represent the current state of a task, and draw
57 * them in their own process.
58 * <p>
59 * When we task becomes visible again, we show a starting window with the snapshot as the content to
60 * make app transitions more responsive.
61 * <p>
62 * To access this class, acquire the global window manager lock.
63 */
64class TaskSnapshotController {
Winson Chung3e13ef82017-06-29 12:41:14 -070065 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
Jorim Jaggi02886a82016-12-06 09:10:06 -080066
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010067 /**
68 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
69 * used as the snapshot.
70 */
71 @VisibleForTesting
72 static final int SNAPSHOT_MODE_REAL = 0;
73
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 dummy representation of the app.
77 */
78 @VisibleForTesting
79 static final int SNAPSHOT_MODE_APP_THEME = 1;
80
81 /**
82 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
83 */
84 @VisibleForTesting
85 static final int SNAPSHOT_MODE_NONE = 2;
86
Jorim Jaggi02886a82016-12-06 09:10:06 -080087 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080088
Jorim Jaggi7361bab2017-01-16 17:17:58 +010089 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010090 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
91 Environment::getDataSystemCeDirectory);
92 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080093 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020094 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080095
Keyvan Amirie681cec2017-06-05 22:48:26 -070096 /**
97 * Flag indicating whether we are running on an Android TV device.
98 */
99 private final boolean mIsRunningOnTv;
100
Juho Haf5dd7cc2017-06-15 11:38:56 +0900101 /**
102 * Flag indicating whether we are running on an IoT device.
103 */
104 private final boolean mIsRunningOnIoT;
105
Matthew Ng95378192017-08-16 11:57:00 -0700106 /**
107 * Flag indicating whether we are running on an Android Wear device.
108 */
109 private final boolean mIsRunningOnWear;
110
Jorim Jaggi02886a82016-12-06 09:10:06 -0800111 TaskSnapshotController(WindowManagerService service) {
112 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100113 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700114 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
115 PackageManager.FEATURE_LEANBACK);
Juho Haf5dd7cc2017-06-15 11:38:56 +0900116 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
117 PackageManager.FEATURE_EMBEDDED);
Matthew Ng95378192017-08-16 11:57:00 -0700118 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
119 PackageManager.FEATURE_WATCH);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800120 }
121
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100122 void systemReady() {
123 mPersister.start();
124 }
125
Jorim Jaggi02886a82016-12-06 09:10:06 -0800126 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100127 handleClosingApps(mService.mClosingApps);
128 }
129
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100130 /**
131 * Called when the visibility of an app changes outside of the regular app transition flow.
132 */
133 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100134 if (!visible) {
135 handleClosingApps(Sets.newArraySet(appWindowToken));
136 }
137 }
138
139 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700140 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100141 return;
142 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800143
144 // We need to take a snapshot of the task if and only if all activities of the task are
145 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100146 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200147 snapshotTasks(mTmpTasks);
148
149 }
150
151 private void snapshotTasks(ArraySet<Task> tasks) {
152 for (int i = tasks.size() - 1; i >= 0; i--) {
153 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100154 final int mode = getSnapshotMode(task);
155 final TaskSnapshot snapshot;
156 switch (mode) {
157 case SNAPSHOT_MODE_NONE:
158 continue;
159 case SNAPSHOT_MODE_APP_THEME:
160 snapshot = drawAppThemeSnapshot(task);
161 break;
162 case SNAPSHOT_MODE_REAL:
163 snapshot = snapshotTask(task);
164 break;
165 default:
166 snapshot = null;
167 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800168 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100169 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700170 final GraphicBuffer buffer = snapshot.getSnapshot();
171 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
172 buffer.destroy();
173 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
174 + buffer.getHeight());
175 } else {
176 mCache.putSnapshot(task, snapshot);
177 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
178 if (task.getController() != null) {
179 task.getController().reportSnapshotChanged(snapshot);
180 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100181 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800182 }
183 }
184 }
185
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100186 /**
187 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
188 * MANAGER LOCK WHEN CALLING THIS METHOD!
189 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100190 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
191 boolean reducedResolution) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700192 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
193 || DISABLE_FULL_SIZED_BITMAPS);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800194 }
195
196 /**
197 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
198 * MANAGER LOCK WHEN CALLING THIS METHOD!
199 */
200 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200201 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800202 return TaskSnapshotSurface.create(mService, token, snapshot);
203 }
204
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100205 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100206 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800207 if (top == null) {
208 return null;
209 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700210 final WindowState mainWindow = top.findMainWindow();
211 if (mainWindow == null) {
212 return null;
213 }
chaviwfbe47df2017-11-10 16:14:49 -0800214 if (!mService.mPolicy.isScreenOn()) {
215 if (DEBUG_SCREENSHOT) {
216 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
217 }
218 return null;
219 }
220 if (task.getSurfaceControl() == null) {
221 return null;
222 }
223
Matthew Ngcb7ac672017-07-21 17:27:42 -0700224 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
225 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
chaviwfbe47df2017-11-10 16:14:49 -0800226 final Rect taskFrame = new Rect();
227 task.getBounds(taskFrame);
228
Chavi Weingartenea2eb5a2017-11-29 21:26:24 +0000229 final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
chaviwfbe47df2017-11-10 16:14:49 -0800230 task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
231
Juho Had864b442017-06-12 20:15:31 +0900232 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
chaviwfbe47df2017-11-10 16:14:49 -0800233 if (DEBUG_SCREENSHOT) {
234 Slog.w(TAG_WM, "Failed to take screenshot");
235 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800236 return null;
237 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100238 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Adrian Roos98a146d2017-11-29 16:39:44 +0100239 getInsetsFromTaskBounds(mainWindow, task),
Matthew Ngcb7ac672017-07-21 17:27:42 -0700240 isLowRamDevice /* reduced */, scaleFraction /* scale */);
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200241 }
242
Keyvan Amirie681cec2017-06-05 22:48:26 -0700243 private boolean shouldDisableSnapshots() {
Jorim Jaggie7d2b852017-08-28 17:55:15 +0200244 return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
Keyvan Amirie681cec2017-06-05 22:48:26 -0700245 }
246
Adrian Roos98a146d2017-11-29 16:39:44 +0100247 private Rect getInsetsFromTaskBounds(WindowState state, Task task) {
248 final Rect r = new Rect();
249 r.set(state.getContentFrameLw());
250 r.intersectUnchecked(state.getStableFrameLw());
251
252 final Rect taskBounds = task.getBounds();
253
254 r.set(Math.max(0, r.left - taskBounds.left),
255 Math.max(0, r.top - taskBounds.top),
256 Math.max(0, taskBounds.right - r.right),
257 Math.max(0, taskBounds.bottom - r.bottom));
258 return r;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800259 }
260
261 /**
262 * Retrieves all closing tasks based on the list of closing apps during an app transition.
263 */
264 @VisibleForTesting
265 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
266 outClosingTasks.clear();
267 for (int i = closingApps.size() - 1; i >= 0; i--) {
268 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800269 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800270
271 // If the task of the app is not visible anymore, it means no other app in that task
272 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800273 if (task != null && !task.isVisible()) {
274 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800275 }
276 }
277 }
278
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100279 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100280 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100281 final AppWindowToken topChild = task.getTopChild();
Wale Ogunwale68278562017-09-23 17:13:55 -0700282 if (!task.isActivityTypeStandardOrUndefined()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100283 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200284 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100285 return SNAPSHOT_MODE_APP_THEME;
286 } else {
287 return SNAPSHOT_MODE_REAL;
288 }
289 }
290
291 /**
292 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
293 * as possible by using the theme's window background.
294 */
295 private TaskSnapshot drawAppThemeSnapshot(Task task) {
296 final AppWindowToken topChild = task.getTopChild();
297 if (topChild == null) {
298 return null;
299 }
300 final WindowState mainWindow = topChild.findMainWindow();
301 if (mainWindow == null) {
302 return null;
303 }
304 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200305 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
306 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200307 final LayoutParams attrs = mainWindow.getAttrs();
308 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
309 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700310 final int width = mainWindow.getFrameLw().width();
311 final int height = mainWindow.getFrameLw().height();
312
313 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
314 node.setLeftTopRightBottom(0, 0, width, height);
315 node.setClipToBounds(false);
316 final DisplayListCanvas c = node.start(width, height);
317 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200318 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
319 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700320 node.end(c);
321 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Winson Chung6267f992017-10-06 14:01:10 -0700322 if (hwBitmap == null) {
323 return null;
324 }
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200325 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
326 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
Matthew Ng30a7f7c2017-09-18 16:29:46 -0700327 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800328 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100329
330 /**
331 * Called when an {@link AppWindowToken} has been removed.
332 */
333 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100334 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100335 }
336
337 /**
338 * Called when the process of an {@link AppWindowToken} has died.
339 */
340 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100341 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100342 }
343
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100344 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100345 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100346 mPersister.onTaskRemovedFromRecents(taskId, userId);
347 }
348
349 /**
350 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
351 */
352 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
353 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
354 }
355
Jorim Jaggia41b7292017-05-11 23:50:34 +0200356 /**
357 * Temporarily pauses/unpauses persisting of task snapshots.
358 *
359 * @param paused Whether task snapshot persisting should be paused.
360 */
361 void setPersisterPaused(boolean paused) {
362 mPersister.setPaused(paused);
363 }
364
Jorim Jaggi51304d72017-05-17 17:25:32 +0200365 /**
366 * Called when screen is being turned off.
367 */
368 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700369 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200370 listener.onScreenOff();
371 return;
372 }
373
374 // We can't take a snapshot when screen is off, so take a snapshot now!
375 mHandler.post(() -> {
376 try {
377 synchronized (mService.mWindowMap) {
378 mTmpTasks.clear();
379 mService.mRoot.forAllTasks(task -> {
380 if (task.isVisible()) {
381 mTmpTasks.add(task);
382 }
383 });
384 snapshotTasks(mTmpTasks);
385 }
386 } finally {
387 listener.onScreenOff();
388 }
389 });
390 }
391
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100392 void dump(PrintWriter pw, String prefix) {
393 mCache.dump(pw, prefix);
394 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800395}