blob: bf8fabd326dbdc1595c804bb6c1dade84be72c26 [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 Jaggi02886a82016-12-06 09:10:06 -080020
21import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010022import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080023import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010024import android.app.ActivityManager.TaskSnapshot;
Keyvan Amirie681cec2017-06-05 22:48:26 -070025import android.content.pm.PackageManager;
Jorim Jaggi6aead1c2017-05-23 15:07:44 +020026import android.graphics.Bitmap;
Jorim Jaggi02886a82016-12-06 09:10:06 -080027import android.graphics.GraphicBuffer;
Jorim Jaggi30d64f32017-04-07 16:33:17 +020028import android.graphics.Rect;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010029import android.os.Environment;
Jorim Jaggi51304d72017-05-17 17:25:32 +020030import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080031import android.util.ArraySet;
Winson Chung91092762017-05-24 14:08:48 -070032import android.view.DisplayListCanvas;
33import android.view.RenderNode;
34import android.view.ThreadedRenderer;
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
Guang Zhu09486a32017-06-06 03:27:44 +000039import 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;
45
Jorim Jaggi02886a82016-12-06 09:10:06 -080046/**
47 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
48 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
49 * like without any copying.
50 * <p>
51 * System applications may retrieve a snapshot to represent the current state of a task, and draw
52 * them in their own process.
53 * <p>
54 * When we task becomes visible again, we show a starting window with the snapshot as the content to
55 * make app transitions more responsive.
56 * <p>
57 * To access this class, acquire the global window manager lock.
58 */
59class TaskSnapshotController {
60
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010061 /**
62 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
63 * used as the snapshot.
64 */
65 @VisibleForTesting
66 static final int SNAPSHOT_MODE_REAL = 0;
67
68 /**
69 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
70 * we should try to use the app theme to create a dummy representation of the app.
71 */
72 @VisibleForTesting
73 static final int SNAPSHOT_MODE_APP_THEME = 1;
74
75 /**
76 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
77 */
78 @VisibleForTesting
79 static final int SNAPSHOT_MODE_NONE = 2;
80
Jorim Jaggi02886a82016-12-06 09:10:06 -080081 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080082
Jorim Jaggi7361bab2017-01-16 17:17:58 +010083 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010084 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
85 Environment::getDataSystemCeDirectory);
86 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080087 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020088 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080089
Keyvan Amirie681cec2017-06-05 22:48:26 -070090 /**
91 * Flag indicating whether we are running on an Android TV device.
92 */
93 private final boolean mIsRunningOnTv;
94
Jorim Jaggi02886a82016-12-06 09:10:06 -080095 TaskSnapshotController(WindowManagerService service) {
96 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010097 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -070098 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
99 PackageManager.FEATURE_LEANBACK);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800100 }
101
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100102 void systemReady() {
103 mPersister.start();
104 }
105
Jorim Jaggi02886a82016-12-06 09:10:06 -0800106 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100107 handleClosingApps(mService.mClosingApps);
108 }
109
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100110 /**
111 * Called when the visibility of an app changes outside of the regular app transition flow.
112 */
113 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100114 if (!visible) {
115 handleClosingApps(Sets.newArraySet(appWindowToken));
116 }
117 }
118
119 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700120 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100121 return;
122 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800123
124 // We need to take a snapshot of the task if and only if all activities of the task are
125 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100126 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200127 snapshotTasks(mTmpTasks);
128
129 }
130
131 private void snapshotTasks(ArraySet<Task> tasks) {
132 for (int i = tasks.size() - 1; i >= 0; i--) {
133 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100134 final int mode = getSnapshotMode(task);
135 final TaskSnapshot snapshot;
136 switch (mode) {
137 case SNAPSHOT_MODE_NONE:
138 continue;
139 case SNAPSHOT_MODE_APP_THEME:
140 snapshot = drawAppThemeSnapshot(task);
141 break;
142 case SNAPSHOT_MODE_REAL:
143 snapshot = snapshotTask(task);
144 break;
145 default:
146 snapshot = null;
147 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800148 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100149 if (snapshot != null) {
150 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100151 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100152 if (task.getController() != null) {
153 task.getController().reportSnapshotChanged(snapshot);
154 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800155 }
156 }
157 }
158
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100159 /**
160 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
161 * MANAGER LOCK WHEN CALLING THIS METHOD!
162 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100163 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
164 boolean reducedResolution) {
165 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800166 }
167
168 /**
169 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
170 * MANAGER LOCK WHEN CALLING THIS METHOD!
171 */
172 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200173 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800174 return TaskSnapshotSurface.create(mService, token, snapshot);
175 }
176
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100177 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100178 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800179 if (top == null) {
180 return null;
181 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700182 final WindowState mainWindow = top.findMainWindow();
183 if (mainWindow == null) {
184 return null;
185 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100186 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
187 -1, -1, false, 1.0f, false, true);
188 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800189 return null;
190 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100191 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200192 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
193 1f /* scale */);
194 }
195
Keyvan Amirie681cec2017-06-05 22:48:26 -0700196 private boolean shouldDisableSnapshots() {
197 return !ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic() || mIsRunningOnTv;
198 }
199
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200200 private Rect minRect(Rect rect1, Rect rect2) {
201 return new Rect(Math.min(rect1.left, rect2.left),
202 Math.min(rect1.top, rect2.top),
203 Math.min(rect1.right, rect2.right),
204 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800205 }
206
207 /**
208 * Retrieves all closing tasks based on the list of closing apps during an app transition.
209 */
210 @VisibleForTesting
211 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
212 outClosingTasks.clear();
213 for (int i = closingApps.size() - 1; i >= 0; i--) {
214 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800215 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800216
217 // If the task of the app is not visible anymore, it means no other app in that task
218 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800219 if (task != null && !task.isVisible()) {
220 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800221 }
222 }
223 }
224
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100225 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100226 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100227 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100228 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
229 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200230 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100231 return SNAPSHOT_MODE_APP_THEME;
232 } else {
233 return SNAPSHOT_MODE_REAL;
234 }
235 }
236
237 /**
238 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
239 * as possible by using the theme's window background.
240 */
241 private TaskSnapshot drawAppThemeSnapshot(Task task) {
242 final AppWindowToken topChild = task.getTopChild();
243 if (topChild == null) {
244 return null;
245 }
246 final WindowState mainWindow = topChild.findMainWindow();
247 if (mainWindow == null) {
248 return null;
249 }
250 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200251 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
252 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200253 final LayoutParams attrs = mainWindow.getAttrs();
254 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
255 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700256 final int width = mainWindow.getFrameLw().width();
257 final int height = mainWindow.getFrameLw().height();
258
259 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
260 node.setLeftTopRightBottom(0, 0, width, height);
261 node.setClipToBounds(false);
262 final DisplayListCanvas c = node.start(width, height);
263 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200264 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
265 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700266 node.end(c);
267 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200268
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200269 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
270 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
271 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800272 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100273
274 /**
275 * Called when an {@link AppWindowToken} has been removed.
276 */
277 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100278 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100279 }
280
281 /**
282 * Called when the process of an {@link AppWindowToken} has died.
283 */
284 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100285 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100286 }
287
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100288 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100289 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100290 mPersister.onTaskRemovedFromRecents(taskId, userId);
291 }
292
293 /**
294 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
295 */
296 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
297 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
298 }
299
Jorim Jaggia41b7292017-05-11 23:50:34 +0200300 /**
301 * Temporarily pauses/unpauses persisting of task snapshots.
302 *
303 * @param paused Whether task snapshot persisting should be paused.
304 */
305 void setPersisterPaused(boolean paused) {
306 mPersister.setPaused(paused);
307 }
308
Jorim Jaggi51304d72017-05-17 17:25:32 +0200309 /**
310 * Called when screen is being turned off.
311 */
312 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700313 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200314 listener.onScreenOff();
315 return;
316 }
317
318 // We can't take a snapshot when screen is off, so take a snapshot now!
319 mHandler.post(() -> {
320 try {
321 synchronized (mService.mWindowMap) {
322 mTmpTasks.clear();
323 mService.mRoot.forAllTasks(task -> {
324 if (task.isVisible()) {
325 mTmpTasks.add(task);
326 }
327 });
328 snapshotTasks(mTmpTasks);
329 }
330 } finally {
331 listener.onScreenOff();
332 }
333 });
334 }
335
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100336 void dump(PrintWriter pw, String prefix) {
337 mCache.dump(pw, prefix);
338 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800339}