blob: b2667781b95593ed8ce480f624c30f91c0619296 [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 Jaggi6aead1c2017-05-23 15:07:44 +020020import 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;
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010028import 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;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020034import android.view.WindowManager.LayoutParams;
Jorim Jaggi51304d72017-05-17 17:25:32 +020035import android.view.WindowManagerPolicy.ScreenOffListener;
Jorim Jaggi02886a82016-12-06 09:10:06 -080036import android.view.WindowManagerPolicy.StartingSurface;
37
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010038import com.google.android.collect.Sets;
39
Jorim Jaggi02886a82016-12-06 09:10:06 -080040import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020041import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080042
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010043import java.io.PrintWriter;
44
Jorim Jaggi02886a82016-12-06 09:10:06 -080045/**
46 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
47 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
48 * like without any copying.
49 * <p>
50 * System applications may retrieve a snapshot to represent the current state of a task, and draw
51 * them in their own process.
52 * <p>
53 * When we task becomes visible again, we show a starting window with the snapshot as the content to
54 * make app transitions more responsive.
55 * <p>
56 * To access this class, acquire the global window manager lock.
57 */
58class TaskSnapshotController {
59
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010060 /**
61 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
62 * used as the snapshot.
63 */
64 @VisibleForTesting
65 static final int SNAPSHOT_MODE_REAL = 0;
66
67 /**
68 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
69 * we should try to use the app theme to create a dummy representation of the app.
70 */
71 @VisibleForTesting
72 static final int SNAPSHOT_MODE_APP_THEME = 1;
73
74 /**
75 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
76 */
77 @VisibleForTesting
78 static final int SNAPSHOT_MODE_NONE = 2;
79
Jorim Jaggi02886a82016-12-06 09:10:06 -080080 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080081
Jorim Jaggi7361bab2017-01-16 17:17:58 +010082 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010083 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
84 Environment::getDataSystemCeDirectory);
85 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080086 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020087 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080088
89 TaskSnapshotController(WindowManagerService service) {
90 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010091 mCache = new TaskSnapshotCache(mService, mLoader);
Jorim Jaggi02886a82016-12-06 09:10:06 -080092 }
93
Jorim Jaggif9084ec2017-01-16 13:16:59 +010094 void systemReady() {
95 mPersister.start();
96 }
97
Jorim Jaggi02886a82016-12-06 09:10:06 -080098 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010099 handleClosingApps(mService.mClosingApps);
100 }
101
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100102 /**
103 * Called when the visibility of an app changes outside of the regular app transition flow.
104 */
105 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100106 if (!visible) {
107 handleClosingApps(Sets.newArraySet(appWindowToken));
108 }
109 }
110
111 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100112 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
113 return;
114 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800115
116 // We need to take a snapshot of the task if and only if all activities of the task are
117 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100118 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200119 snapshotTasks(mTmpTasks);
120
121 }
122
123 private void snapshotTasks(ArraySet<Task> tasks) {
124 for (int i = tasks.size() - 1; i >= 0; i--) {
125 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100126 final int mode = getSnapshotMode(task);
127 final TaskSnapshot snapshot;
128 switch (mode) {
129 case SNAPSHOT_MODE_NONE:
130 continue;
131 case SNAPSHOT_MODE_APP_THEME:
132 snapshot = drawAppThemeSnapshot(task);
133 break;
134 case SNAPSHOT_MODE_REAL:
135 snapshot = snapshotTask(task);
136 break;
137 default:
138 snapshot = null;
139 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800140 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100141 if (snapshot != null) {
142 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100143 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100144 if (task.getController() != null) {
145 task.getController().reportSnapshotChanged(snapshot);
146 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800147 }
148 }
149 }
150
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100151 /**
152 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
153 * MANAGER LOCK WHEN CALLING THIS METHOD!
154 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100155 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
156 boolean reducedResolution) {
157 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800158 }
159
160 /**
161 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
162 * MANAGER LOCK WHEN CALLING THIS METHOD!
163 */
164 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200165 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800166 return TaskSnapshotSurface.create(mService, token, snapshot);
167 }
168
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100169 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100170 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800171 if (top == null) {
172 return null;
173 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700174 final WindowState mainWindow = top.findMainWindow();
175 if (mainWindow == null) {
176 return null;
177 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100178 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
179 -1, -1, false, 1.0f, false, true);
180 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800181 return null;
182 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100183 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200184 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
185 1f /* scale */);
186 }
187
188 private Rect minRect(Rect rect1, Rect rect2) {
189 return new Rect(Math.min(rect1.left, rect2.left),
190 Math.min(rect1.top, rect2.top),
191 Math.min(rect1.right, rect2.right),
192 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800193 }
194
195 /**
196 * Retrieves all closing tasks based on the list of closing apps during an app transition.
197 */
198 @VisibleForTesting
199 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
200 outClosingTasks.clear();
201 for (int i = closingApps.size() - 1; i >= 0; i--) {
202 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800203 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800204
205 // If the task of the app is not visible anymore, it means no other app in that task
206 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800207 if (task != null && !task.isVisible()) {
208 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800209 }
210 }
211 }
212
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100213 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100214 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100215 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100216 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
217 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200218 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100219 return SNAPSHOT_MODE_APP_THEME;
220 } else {
221 return SNAPSHOT_MODE_REAL;
222 }
223 }
224
225 /**
226 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
227 * as possible by using the theme's window background.
228 */
229 private TaskSnapshot drawAppThemeSnapshot(Task task) {
230 final AppWindowToken topChild = task.getTopChild();
231 if (topChild == null) {
232 return null;
233 }
234 final WindowState mainWindow = topChild.findMainWindow();
235 if (mainWindow == null) {
236 return null;
237 }
238 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200239 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
240 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200241 final Bitmap b = Bitmap.createBitmap(mainWindow.getFrameLw().width(),
242 mainWindow.getFrameLw().height(), ARGB_8888);
243 final Canvas c = new Canvas(b);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100244 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200245 final LayoutParams attrs = mainWindow.getAttrs();
246 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
247 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
248 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
249 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200250
251 // Flush writer.
252 c.setBitmap(null);
253 final Bitmap hwBitmap = b.copy(HARDWARE, false /* isMutable */);
254 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
255 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
256 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800257 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100258
259 /**
260 * Called when an {@link AppWindowToken} has been removed.
261 */
262 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100263 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100264 }
265
266 /**
267 * Called when the process of an {@link AppWindowToken} has died.
268 */
269 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100270 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100271 }
272
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100273 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100274 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100275 mPersister.onTaskRemovedFromRecents(taskId, userId);
276 }
277
278 /**
279 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
280 */
281 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
282 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
283 }
284
Jorim Jaggia41b7292017-05-11 23:50:34 +0200285 /**
286 * Temporarily pauses/unpauses persisting of task snapshots.
287 *
288 * @param paused Whether task snapshot persisting should be paused.
289 */
290 void setPersisterPaused(boolean paused) {
291 mPersister.setPaused(paused);
292 }
293
Jorim Jaggi51304d72017-05-17 17:25:32 +0200294 /**
295 * Called when screen is being turned off.
296 */
297 void screenTurningOff(ScreenOffListener listener) {
298 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
299 listener.onScreenOff();
300 return;
301 }
302
303 // We can't take a snapshot when screen is off, so take a snapshot now!
304 mHandler.post(() -> {
305 try {
306 synchronized (mService.mWindowMap) {
307 mTmpTasks.clear();
308 mService.mRoot.forAllTasks(task -> {
309 if (task.isVisible()) {
310 mTmpTasks.add(task);
311 }
312 });
313 snapshotTasks(mTmpTasks);
314 }
315 } finally {
316 listener.onScreenOff();
317 }
318 });
319 }
320
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100321 void dump(PrintWriter pw, String prefix) {
322 mCache.dump(pw, prefix);
323 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800324}