blob: 1bbe1d0c9a93ced07ee0ebf4cf3e73c723462123 [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;
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010020import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
21import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
22import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_RARELY;
23import static android.graphics.PixelFormat.RGBA_8888;
Jorim Jaggi02886a82016-12-06 09:10:06 -080024
25import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010026import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080027import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010028import android.app.ActivityManager.TaskSnapshot;
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010029import android.graphics.Canvas;
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 Jaggi02886a82016-12-06 09:10:06 -080033import android.util.ArraySet;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020034import android.view.WindowManager.LayoutParams;
Jorim Jaggi02886a82016-12-06 09:10:06 -080035import android.view.WindowManagerPolicy.StartingSurface;
36
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010037import com.google.android.collect.Sets;
38
Jorim Jaggi02886a82016-12-06 09:10:06 -080039import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020040import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080041
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010042import java.io.PrintWriter;
43
Jorim Jaggi02886a82016-12-06 09:10:06 -080044/**
45 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
46 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
47 * like without any copying.
48 * <p>
49 * System applications may retrieve a snapshot to represent the current state of a task, and draw
50 * them in their own process.
51 * <p>
52 * When we task becomes visible again, we show a starting window with the snapshot as the content to
53 * make app transitions more responsive.
54 * <p>
55 * To access this class, acquire the global window manager lock.
56 */
57class TaskSnapshotController {
58
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010059 /**
60 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
61 * used as the snapshot.
62 */
63 @VisibleForTesting
64 static final int SNAPSHOT_MODE_REAL = 0;
65
66 /**
67 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
68 * we should try to use the app theme to create a dummy representation of the app.
69 */
70 @VisibleForTesting
71 static final int SNAPSHOT_MODE_APP_THEME = 1;
72
73 /**
74 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
75 */
76 @VisibleForTesting
77 static final int SNAPSHOT_MODE_NONE = 2;
78
Jorim Jaggi02886a82016-12-06 09:10:06 -080079 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080080
Jorim Jaggi7361bab2017-01-16 17:17:58 +010081 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010082 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
83 Environment::getDataSystemCeDirectory);
84 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080085 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
86
87 TaskSnapshotController(WindowManagerService service) {
88 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010089 mCache = new TaskSnapshotCache(mService, mLoader);
Jorim Jaggi02886a82016-12-06 09:10:06 -080090 }
91
Jorim Jaggif9084ec2017-01-16 13:16:59 +010092 void systemReady() {
93 mPersister.start();
94 }
95
Jorim Jaggi02886a82016-12-06 09:10:06 -080096 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010097 handleClosingApps(mService.mClosingApps);
98 }
99
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100100 /**
101 * Called when the visibility of an app changes outside of the regular app transition flow.
102 */
103 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100104 if (!visible) {
105 handleClosingApps(Sets.newArraySet(appWindowToken));
106 }
107 }
108
109 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100110 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
111 return;
112 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800113
114 // We need to take a snapshot of the task if and only if all activities of the task are
115 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100116 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800117 for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
118 final Task task = mTmpTasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100119 final int mode = getSnapshotMode(task);
120 final TaskSnapshot snapshot;
121 switch (mode) {
122 case SNAPSHOT_MODE_NONE:
123 continue;
124 case SNAPSHOT_MODE_APP_THEME:
125 snapshot = drawAppThemeSnapshot(task);
126 break;
127 case SNAPSHOT_MODE_REAL:
128 snapshot = snapshotTask(task);
129 break;
130 default:
131 snapshot = null;
132 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800133 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100134 if (snapshot != null) {
135 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100136 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100137 if (task.getController() != null) {
138 task.getController().reportSnapshotChanged(snapshot);
139 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800140 }
141 }
142 }
143
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100144 /**
145 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
146 * MANAGER LOCK WHEN CALLING THIS METHOD!
147 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100148 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
149 boolean reducedResolution) {
150 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800151 }
152
153 /**
154 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
155 * MANAGER LOCK WHEN CALLING THIS METHOD!
156 */
157 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200158 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800159 return TaskSnapshotSurface.create(mService, token, snapshot);
160 }
161
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100162 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100163 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800164 if (top == null) {
165 return null;
166 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700167 final WindowState mainWindow = top.findMainWindow();
168 if (mainWindow == null) {
169 return null;
170 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100171 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
172 -1, -1, false, 1.0f, false, true);
173 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800174 return null;
175 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100176 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200177 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
178 1f /* scale */);
179 }
180
181 private Rect minRect(Rect rect1, Rect rect2) {
182 return new Rect(Math.min(rect1.left, rect2.left),
183 Math.min(rect1.top, rect2.top),
184 Math.min(rect1.right, rect2.right),
185 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800186 }
187
188 /**
189 * Retrieves all closing tasks based on the list of closing apps during an app transition.
190 */
191 @VisibleForTesting
192 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
193 outClosingTasks.clear();
194 for (int i = closingApps.size() - 1; i >= 0; i--) {
195 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800196 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800197
198 // If the task of the app is not visible anymore, it means no other app in that task
199 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800200 if (task != null && !task.isVisible()) {
201 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800202 }
203 }
204 }
205
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100206 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100207 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100208 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100209 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
210 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200211 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100212 return SNAPSHOT_MODE_APP_THEME;
213 } else {
214 return SNAPSHOT_MODE_REAL;
215 }
216 }
217
218 /**
219 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
220 * as possible by using the theme's window background.
221 */
222 private TaskSnapshot drawAppThemeSnapshot(Task task) {
223 final AppWindowToken topChild = task.getTopChild();
224 if (topChild == null) {
225 return null;
226 }
227 final WindowState mainWindow = topChild.findMainWindow();
228 if (mainWindow == null) {
229 return null;
230 }
231 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200232 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
233 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100234 final GraphicBuffer buffer = GraphicBuffer.create(mainWindow.getFrameLw().width(),
235 mainWindow.getFrameLw().height(),
236 RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_RARELY | USAGE_SW_READ_NEVER);
237 if (buffer == null) {
238 return null;
239 }
240 final Canvas c = buffer.lockCanvas();
241 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200242 final LayoutParams attrs = mainWindow.getAttrs();
243 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
244 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
245 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
246 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100247 buffer.unlockCanvasAndPost(c);
248 return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
249 mainWindow.mStableInsets, false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800250 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100251
252 /**
253 * Called when an {@link AppWindowToken} has been removed.
254 */
255 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100256 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100257 }
258
259 /**
260 * Called when the process of an {@link AppWindowToken} has died.
261 */
262 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100263 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100264 }
265
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100266 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100267 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100268 mPersister.onTaskRemovedFromRecents(taskId, userId);
269 }
270
271 /**
272 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
273 */
274 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
275 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
276 }
277
Jorim Jaggia41b7292017-05-11 23:50:34 +0200278 /**
279 * Temporarily pauses/unpauses persisting of task snapshots.
280 *
281 * @param paused Whether task snapshot persisting should be paused.
282 */
283 void setPersisterPaused(boolean paused) {
284 mPersister.setPaused(paused);
285 }
286
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100287 void dump(PrintWriter pw, String prefix) {
288 mCache.dump(pw, prefix);
289 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800290}