blob: 1f7ef5014d3653abc991494eb8c6b480f58795e6 [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
19import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
Guang Zhu09486a32017-06-06 03:27:44 +000020import static android.graphics.Bitmap.Config.ARGB_8888;
21import static android.graphics.Bitmap.Config.HARDWARE;
Jorim Jaggi02886a82016-12-06 09:10:06 -080022
23import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010024import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080025import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010026import android.app.ActivityManager.TaskSnapshot;
Jorim Jaggi6aead1c2017-05-23 15:07:44 +020027import android.graphics.Bitmap;
Guang Zhu09486a32017-06-06 03:27:44 +000028import android.graphics.Canvas;
Jorim Jaggi02886a82016-12-06 09:10:06 -080029import android.graphics.GraphicBuffer;
Jorim Jaggi30d64f32017-04-07 16:33:17 +020030import android.graphics.Rect;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010031import android.os.Environment;
Jorim Jaggi51304d72017-05-17 17:25:32 +020032import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080033import android.util.ArraySet;
Winson Chung91092762017-05-24 14:08:48 -070034import android.view.DisplayListCanvas;
35import android.view.RenderNode;
36import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020037import android.view.WindowManager.LayoutParams;
Jorim Jaggi51304d72017-05-17 17:25:32 +020038import android.view.WindowManagerPolicy.ScreenOffListener;
Jorim Jaggi02886a82016-12-06 09:10:06 -080039import android.view.WindowManagerPolicy.StartingSurface;
40
Guang Zhu09486a32017-06-06 03:27:44 +000041import com.google.android.collect.Sets;
42
Jorim Jaggi02886a82016-12-06 09:10:06 -080043import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020044import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080045
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010046import java.io.PrintWriter;
47
Jorim Jaggi02886a82016-12-06 09:10:06 -080048/**
49 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
50 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
51 * like without any copying.
52 * <p>
53 * System applications may retrieve a snapshot to represent the current state of a task, and draw
54 * them in their own process.
55 * <p>
56 * When we task becomes visible again, we show a starting window with the snapshot as the content to
57 * make app transitions more responsive.
58 * <p>
59 * To access this class, acquire the global window manager lock.
60 */
61class TaskSnapshotController {
62
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010063 /**
64 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
65 * used as the snapshot.
66 */
67 @VisibleForTesting
68 static final int SNAPSHOT_MODE_REAL = 0;
69
70 /**
71 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
72 * we should try to use the app theme to create a dummy representation of the app.
73 */
74 @VisibleForTesting
75 static final int SNAPSHOT_MODE_APP_THEME = 1;
76
77 /**
78 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
79 */
80 @VisibleForTesting
81 static final int SNAPSHOT_MODE_NONE = 2;
82
Jorim Jaggi02886a82016-12-06 09:10:06 -080083 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080084
Jorim Jaggi7361bab2017-01-16 17:17:58 +010085 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010086 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
87 Environment::getDataSystemCeDirectory);
88 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080089 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020090 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080091
92 TaskSnapshotController(WindowManagerService service) {
93 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010094 mCache = new TaskSnapshotCache(mService, mLoader);
Jorim Jaggi02886a82016-12-06 09:10:06 -080095 }
96
Jorim Jaggif9084ec2017-01-16 13:16:59 +010097 void systemReady() {
98 mPersister.start();
99 }
100
Jorim Jaggi02886a82016-12-06 09:10:06 -0800101 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100102 handleClosingApps(mService.mClosingApps);
103 }
104
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100105 /**
106 * Called when the visibility of an app changes outside of the regular app transition flow.
107 */
108 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100109 if (!visible) {
110 handleClosingApps(Sets.newArraySet(appWindowToken));
111 }
112 }
113
114 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Guang Zhu09486a32017-06-06 03:27:44 +0000115 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100116 return;
117 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800118
119 // We need to take a snapshot of the task if and only if all activities of the task are
120 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100121 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200122 snapshotTasks(mTmpTasks);
123
124 }
125
126 private void snapshotTasks(ArraySet<Task> tasks) {
127 for (int i = tasks.size() - 1; i >= 0; i--) {
128 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100129 final int mode = getSnapshotMode(task);
130 final TaskSnapshot snapshot;
131 switch (mode) {
132 case SNAPSHOT_MODE_NONE:
133 continue;
134 case SNAPSHOT_MODE_APP_THEME:
135 snapshot = drawAppThemeSnapshot(task);
136 break;
137 case SNAPSHOT_MODE_REAL:
138 snapshot = snapshotTask(task);
139 break;
140 default:
141 snapshot = null;
142 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800143 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100144 if (snapshot != null) {
145 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100146 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100147 if (task.getController() != null) {
148 task.getController().reportSnapshotChanged(snapshot);
149 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800150 }
151 }
152 }
153
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100154 /**
155 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
156 * MANAGER LOCK WHEN CALLING THIS METHOD!
157 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100158 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
159 boolean reducedResolution) {
160 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800161 }
162
163 /**
164 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
165 * MANAGER LOCK WHEN CALLING THIS METHOD!
166 */
167 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200168 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800169 return TaskSnapshotSurface.create(mService, token, snapshot);
170 }
171
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100172 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100173 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800174 if (top == null) {
175 return null;
176 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700177 final WindowState mainWindow = top.findMainWindow();
178 if (mainWindow == null) {
179 return null;
180 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100181 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
182 -1, -1, false, 1.0f, false, true);
183 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800184 return null;
185 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100186 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200187 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
188 1f /* scale */);
189 }
190
191 private Rect minRect(Rect rect1, Rect rect2) {
192 return new Rect(Math.min(rect1.left, rect2.left),
193 Math.min(rect1.top, rect2.top),
194 Math.min(rect1.right, rect2.right),
195 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800196 }
197
198 /**
199 * Retrieves all closing tasks based on the list of closing apps during an app transition.
200 */
201 @VisibleForTesting
202 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
203 outClosingTasks.clear();
204 for (int i = closingApps.size() - 1; i >= 0; i--) {
205 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800206 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800207
208 // If the task of the app is not visible anymore, it means no other app in that task
209 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800210 if (task != null && !task.isVisible()) {
211 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800212 }
213 }
214 }
215
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100216 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100217 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100218 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100219 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
220 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200221 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100222 return SNAPSHOT_MODE_APP_THEME;
223 } else {
224 return SNAPSHOT_MODE_REAL;
225 }
226 }
227
228 /**
229 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
230 * as possible by using the theme's window background.
231 */
232 private TaskSnapshot drawAppThemeSnapshot(Task task) {
233 final AppWindowToken topChild = task.getTopChild();
234 if (topChild == null) {
235 return null;
236 }
237 final WindowState mainWindow = topChild.findMainWindow();
238 if (mainWindow == null) {
239 return null;
240 }
241 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200242 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
243 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200244 final LayoutParams attrs = mainWindow.getAttrs();
245 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
246 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700247 final int width = mainWindow.getFrameLw().width();
248 final int height = mainWindow.getFrameLw().height();
249
250 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
251 node.setLeftTopRightBottom(0, 0, width, height);
252 node.setClipToBounds(false);
253 final DisplayListCanvas c = node.start(width, height);
254 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200255 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
256 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700257 node.end(c);
258 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200259
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200260 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
261 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
262 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800263 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100264
265 /**
266 * Called when an {@link AppWindowToken} has been removed.
267 */
268 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100269 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100270 }
271
272 /**
273 * Called when the process of an {@link AppWindowToken} has died.
274 */
275 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100276 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100277 }
278
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100279 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100280 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100281 mPersister.onTaskRemovedFromRecents(taskId, userId);
282 }
283
284 /**
285 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
286 */
287 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
288 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
289 }
290
Jorim Jaggia41b7292017-05-11 23:50:34 +0200291 /**
292 * Temporarily pauses/unpauses persisting of task snapshots.
293 *
294 * @param paused Whether task snapshot persisting should be paused.
295 */
296 void setPersisterPaused(boolean paused) {
297 mPersister.setPaused(paused);
298 }
299
Jorim Jaggi51304d72017-05-17 17:25:32 +0200300 /**
301 * Called when screen is being turned off.
302 */
303 void screenTurningOff(ScreenOffListener listener) {
Guang Zhu09486a32017-06-06 03:27:44 +0000304 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200305 listener.onScreenOff();
306 return;
307 }
308
309 // We can't take a snapshot when screen is off, so take a snapshot now!
310 mHandler.post(() -> {
311 try {
312 synchronized (mService.mWindowMap) {
313 mTmpTasks.clear();
314 mService.mRoot.forAllTasks(task -> {
315 if (task.isVisible()) {
316 mTmpTasks.add(task);
317 }
318 });
319 snapshotTasks(mTmpTasks);
320 }
321 } finally {
322 listener.onScreenOff();
323 }
324 });
325 }
326
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100327 void dump(PrintWriter pw, String prefix) {
328 mCache.dump(pw, prefix);
329 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800330}