blob: 24cb464b59934e5cce02d0993f608ebe29a9b5c6 [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 Jaggi51304d72017-05-17 17:25:32 +020033import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080034import android.util.ArraySet;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020035import android.view.WindowManager.LayoutParams;
Jorim Jaggi51304d72017-05-17 17:25:32 +020036import android.view.WindowManagerPolicy.ScreenOffListener;
Jorim Jaggi02886a82016-12-06 09:10:06 -080037import android.view.WindowManagerPolicy.StartingSurface;
38
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010039import com.google.android.collect.Sets;
40
Jorim Jaggi02886a82016-12-06 09:10:06 -080041import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020042import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080043
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010044import java.io.PrintWriter;
Jorim Jaggi51304d72017-05-17 17:25:32 +020045import java.util.function.Consumer;
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010046
Jorim Jaggi02886a82016-12-06 09:10:06 -080047/**
48 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
49 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
50 * like without any copying.
51 * <p>
52 * System applications may retrieve a snapshot to represent the current state of a task, and draw
53 * them in their own process.
54 * <p>
55 * When we task becomes visible again, we show a starting window with the snapshot as the content to
56 * make app transitions more responsive.
57 * <p>
58 * To access this class, acquire the global window manager lock.
59 */
60class TaskSnapshotController {
61
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010062 /**
63 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
64 * used as the snapshot.
65 */
66 @VisibleForTesting
67 static final int SNAPSHOT_MODE_REAL = 0;
68
69 /**
70 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
71 * we should try to use the app theme to create a dummy representation of the app.
72 */
73 @VisibleForTesting
74 static final int SNAPSHOT_MODE_APP_THEME = 1;
75
76 /**
77 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
78 */
79 @VisibleForTesting
80 static final int SNAPSHOT_MODE_NONE = 2;
81
Jorim Jaggi02886a82016-12-06 09:10:06 -080082 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080083
Jorim Jaggi7361bab2017-01-16 17:17:58 +010084 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010085 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
86 Environment::getDataSystemCeDirectory);
87 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080088 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020089 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080090
91 TaskSnapshotController(WindowManagerService service) {
92 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010093 mCache = new TaskSnapshotCache(mService, mLoader);
Jorim Jaggi02886a82016-12-06 09:10:06 -080094 }
95
Jorim Jaggif9084ec2017-01-16 13:16:59 +010096 void systemReady() {
97 mPersister.start();
98 }
99
Jorim Jaggi02886a82016-12-06 09:10:06 -0800100 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100101 handleClosingApps(mService.mClosingApps);
102 }
103
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100104 /**
105 * Called when the visibility of an app changes outside of the regular app transition flow.
106 */
107 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100108 if (!visible) {
109 handleClosingApps(Sets.newArraySet(appWindowToken));
110 }
111 }
112
113 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100114 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
115 return;
116 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800117
118 // We need to take a snapshot of the task if and only if all activities of the task are
119 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100120 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200121 snapshotTasks(mTmpTasks);
122
123 }
124
125 private void snapshotTasks(ArraySet<Task> tasks) {
126 for (int i = tasks.size() - 1; i >= 0; i--) {
127 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100128 final int mode = getSnapshotMode(task);
129 final TaskSnapshot snapshot;
130 switch (mode) {
131 case SNAPSHOT_MODE_NONE:
132 continue;
133 case SNAPSHOT_MODE_APP_THEME:
134 snapshot = drawAppThemeSnapshot(task);
135 break;
136 case SNAPSHOT_MODE_REAL:
137 snapshot = snapshotTask(task);
138 break;
139 default:
140 snapshot = null;
141 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800142 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100143 if (snapshot != null) {
144 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100145 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100146 if (task.getController() != null) {
147 task.getController().reportSnapshotChanged(snapshot);
148 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800149 }
150 }
151 }
152
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100153 /**
154 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
155 * MANAGER LOCK WHEN CALLING THIS METHOD!
156 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100157 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
158 boolean reducedResolution) {
159 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800160 }
161
162 /**
163 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
164 * MANAGER LOCK WHEN CALLING THIS METHOD!
165 */
166 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200167 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800168 return TaskSnapshotSurface.create(mService, token, snapshot);
169 }
170
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100171 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100172 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800173 if (top == null) {
174 return null;
175 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700176 final WindowState mainWindow = top.findMainWindow();
177 if (mainWindow == null) {
178 return null;
179 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100180 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
181 -1, -1, false, 1.0f, false, true);
182 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800183 return null;
184 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100185 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200186 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
187 1f /* scale */);
188 }
189
190 private Rect minRect(Rect rect1, Rect rect2) {
191 return new Rect(Math.min(rect1.left, rect2.left),
192 Math.min(rect1.top, rect2.top),
193 Math.min(rect1.right, rect2.right),
194 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800195 }
196
197 /**
198 * Retrieves all closing tasks based on the list of closing apps during an app transition.
199 */
200 @VisibleForTesting
201 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
202 outClosingTasks.clear();
203 for (int i = closingApps.size() - 1; i >= 0; i--) {
204 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800205 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800206
207 // If the task of the app is not visible anymore, it means no other app in that task
208 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800209 if (task != null && !task.isVisible()) {
210 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800211 }
212 }
213 }
214
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100215 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100216 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100217 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100218 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
219 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200220 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100221 return SNAPSHOT_MODE_APP_THEME;
222 } else {
223 return SNAPSHOT_MODE_REAL;
224 }
225 }
226
227 /**
228 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
229 * as possible by using the theme's window background.
230 */
231 private TaskSnapshot drawAppThemeSnapshot(Task task) {
232 final AppWindowToken topChild = task.getTopChild();
233 if (topChild == null) {
234 return null;
235 }
236 final WindowState mainWindow = topChild.findMainWindow();
237 if (mainWindow == null) {
238 return null;
239 }
240 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200241 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
242 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100243 final GraphicBuffer buffer = GraphicBuffer.create(mainWindow.getFrameLw().width(),
244 mainWindow.getFrameLw().height(),
245 RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_RARELY | USAGE_SW_READ_NEVER);
246 if (buffer == null) {
247 return null;
248 }
249 final Canvas c = buffer.lockCanvas();
250 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200251 final LayoutParams attrs = mainWindow.getAttrs();
252 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
253 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
254 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
255 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100256 buffer.unlockCanvasAndPost(c);
257 return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
258 mainWindow.mStableInsets, false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800259 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100260
261 /**
262 * Called when an {@link AppWindowToken} has been removed.
263 */
264 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100265 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100266 }
267
268 /**
269 * Called when the process of an {@link AppWindowToken} has died.
270 */
271 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100272 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100273 }
274
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100275 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100276 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100277 mPersister.onTaskRemovedFromRecents(taskId, userId);
278 }
279
280 /**
281 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
282 */
283 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
284 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
285 }
286
Jorim Jaggia41b7292017-05-11 23:50:34 +0200287 /**
288 * Temporarily pauses/unpauses persisting of task snapshots.
289 *
290 * @param paused Whether task snapshot persisting should be paused.
291 */
292 void setPersisterPaused(boolean paused) {
293 mPersister.setPaused(paused);
294 }
295
Jorim Jaggi51304d72017-05-17 17:25:32 +0200296 /**
297 * Called when screen is being turned off.
298 */
299 void screenTurningOff(ScreenOffListener listener) {
300 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
301 listener.onScreenOff();
302 return;
303 }
304
305 // We can't take a snapshot when screen is off, so take a snapshot now!
306 mHandler.post(() -> {
307 try {
308 synchronized (mService.mWindowMap) {
309 mTmpTasks.clear();
310 mService.mRoot.forAllTasks(task -> {
311 if (task.isVisible()) {
312 mTmpTasks.add(task);
313 }
314 });
315 snapshotTasks(mTmpTasks);
316 }
317 } finally {
318 listener.onScreenOff();
319 }
320 });
321 }
322
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100323 void dump(PrintWriter pw, String prefix) {
324 mCache.dump(pw, prefix);
325 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800326}