blob: b8b9b9a25ac8fa3fdbd31683004f13d94ffdfb6a [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
Winson Chung3e13ef82017-06-29 12:41:14 -070021import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23
Jorim Jaggi02886a82016-12-06 09:10:06 -080024import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010025import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080026import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010027import android.app.ActivityManager.TaskSnapshot;
Keyvan Amirie681cec2017-06-05 22:48:26 -070028import android.content.pm.PackageManager;
Jorim Jaggi6aead1c2017-05-23 15:07:44 +020029import android.graphics.Bitmap;
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;
Winson Chung3e13ef82017-06-29 12:41:14 -070035import android.util.Slog;
Winson Chung91092762017-05-24 14:08:48 -070036import android.view.DisplayListCanvas;
37import android.view.RenderNode;
38import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020039import android.view.WindowManager.LayoutParams;
Jorim Jaggi51304d72017-05-17 17:25:32 +020040import android.view.WindowManagerPolicy.ScreenOffListener;
Jorim Jaggi02886a82016-12-06 09:10:06 -080041import android.view.WindowManagerPolicy.StartingSurface;
42
Guang Zhu09486a32017-06-06 03:27:44 +000043import com.google.android.collect.Sets;
44
Jorim Jaggi02886a82016-12-06 09:10:06 -080045import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020046import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080047
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010048import java.io.PrintWriter;
49
Jorim Jaggi02886a82016-12-06 09:10:06 -080050/**
51 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
52 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
53 * like without any copying.
54 * <p>
55 * System applications may retrieve a snapshot to represent the current state of a task, and draw
56 * them in their own process.
57 * <p>
58 * When we task becomes visible again, we show a starting window with the snapshot as the content to
59 * make app transitions more responsive.
60 * <p>
61 * To access this class, acquire the global window manager lock.
62 */
63class TaskSnapshotController {
Winson Chung3e13ef82017-06-29 12:41:14 -070064 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
Jorim Jaggi02886a82016-12-06 09:10:06 -080065
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010066 /**
67 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
68 * used as the snapshot.
69 */
70 @VisibleForTesting
71 static final int SNAPSHOT_MODE_REAL = 0;
72
73 /**
74 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
75 * we should try to use the app theme to create a dummy representation of the app.
76 */
77 @VisibleForTesting
78 static final int SNAPSHOT_MODE_APP_THEME = 1;
79
80 /**
81 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
82 */
83 @VisibleForTesting
84 static final int SNAPSHOT_MODE_NONE = 2;
85
Jorim Jaggi02886a82016-12-06 09:10:06 -080086 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080087
Jorim Jaggi7361bab2017-01-16 17:17:58 +010088 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010089 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
90 Environment::getDataSystemCeDirectory);
91 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080092 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020093 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080094
Keyvan Amirie681cec2017-06-05 22:48:26 -070095 /**
96 * Flag indicating whether we are running on an Android TV device.
97 */
98 private final boolean mIsRunningOnTv;
99
Jorim Jaggi02886a82016-12-06 09:10:06 -0800100 TaskSnapshotController(WindowManagerService service) {
101 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100102 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700103 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
104 PackageManager.FEATURE_LEANBACK);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800105 }
106
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100107 void systemReady() {
108 mPersister.start();
109 }
110
Jorim Jaggi02886a82016-12-06 09:10:06 -0800111 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100112 handleClosingApps(mService.mClosingApps);
113 }
114
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100115 /**
116 * Called when the visibility of an app changes outside of the regular app transition flow.
117 */
118 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100119 if (!visible) {
120 handleClosingApps(Sets.newArraySet(appWindowToken));
121 }
122 }
123
124 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700125 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100126 return;
127 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800128
129 // We need to take a snapshot of the task if and only if all activities of the task are
130 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100131 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200132 snapshotTasks(mTmpTasks);
133
134 }
135
136 private void snapshotTasks(ArraySet<Task> tasks) {
137 for (int i = tasks.size() - 1; i >= 0; i--) {
138 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100139 final int mode = getSnapshotMode(task);
140 final TaskSnapshot snapshot;
141 switch (mode) {
142 case SNAPSHOT_MODE_NONE:
143 continue;
144 case SNAPSHOT_MODE_APP_THEME:
145 snapshot = drawAppThemeSnapshot(task);
146 break;
147 case SNAPSHOT_MODE_REAL:
148 snapshot = snapshotTask(task);
149 break;
150 default:
151 snapshot = null;
152 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800153 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100154 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700155 final GraphicBuffer buffer = snapshot.getSnapshot();
156 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
157 buffer.destroy();
158 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
159 + buffer.getHeight());
160 } else {
161 mCache.putSnapshot(task, snapshot);
162 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
163 if (task.getController() != null) {
164 task.getController().reportSnapshotChanged(snapshot);
165 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100166 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800167 }
168 }
169 }
170
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100171 /**
172 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
173 * MANAGER LOCK WHEN CALLING THIS METHOD!
174 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100175 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
176 boolean reducedResolution) {
177 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800178 }
179
180 /**
181 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
182 * MANAGER LOCK WHEN CALLING THIS METHOD!
183 */
184 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200185 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800186 return TaskSnapshotSurface.create(mService, token, snapshot);
187 }
188
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100189 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100190 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800191 if (top == null) {
192 return null;
193 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700194 final WindowState mainWindow = top.findMainWindow();
195 if (mainWindow == null) {
196 return null;
197 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100198 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
199 -1, -1, false, 1.0f, false, true);
200 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800201 return null;
202 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100203 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200204 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
205 1f /* scale */);
206 }
207
Keyvan Amirie681cec2017-06-05 22:48:26 -0700208 private boolean shouldDisableSnapshots() {
209 return !ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic() || mIsRunningOnTv;
210 }
211
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200212 private Rect minRect(Rect rect1, Rect rect2) {
213 return new Rect(Math.min(rect1.left, rect2.left),
214 Math.min(rect1.top, rect2.top),
215 Math.min(rect1.right, rect2.right),
216 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800217 }
218
219 /**
220 * Retrieves all closing tasks based on the list of closing apps during an app transition.
221 */
222 @VisibleForTesting
223 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
224 outClosingTasks.clear();
225 for (int i = closingApps.size() - 1; i >= 0; i--) {
226 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800227 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800228
229 // If the task of the app is not visible anymore, it means no other app in that task
230 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800231 if (task != null && !task.isVisible()) {
232 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800233 }
234 }
235 }
236
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100237 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100238 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100239 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100240 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
241 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200242 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100243 return SNAPSHOT_MODE_APP_THEME;
244 } else {
245 return SNAPSHOT_MODE_REAL;
246 }
247 }
248
249 /**
250 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
251 * as possible by using the theme's window background.
252 */
253 private TaskSnapshot drawAppThemeSnapshot(Task task) {
254 final AppWindowToken topChild = task.getTopChild();
255 if (topChild == null) {
256 return null;
257 }
258 final WindowState mainWindow = topChild.findMainWindow();
259 if (mainWindow == null) {
260 return null;
261 }
262 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200263 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
264 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200265 final LayoutParams attrs = mainWindow.getAttrs();
266 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
267 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700268 final int width = mainWindow.getFrameLw().width();
269 final int height = mainWindow.getFrameLw().height();
270
271 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
272 node.setLeftTopRightBottom(0, 0, width, height);
273 node.setClipToBounds(false);
274 final DisplayListCanvas c = node.start(width, height);
275 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200276 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
277 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700278 node.end(c);
279 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200280
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200281 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
282 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
283 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800284 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100285
286 /**
287 * Called when an {@link AppWindowToken} has been removed.
288 */
289 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100290 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100291 }
292
293 /**
294 * Called when the process of an {@link AppWindowToken} has died.
295 */
296 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100297 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100298 }
299
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100300 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100301 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100302 mPersister.onTaskRemovedFromRecents(taskId, userId);
303 }
304
305 /**
306 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
307 */
308 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
309 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
310 }
311
Jorim Jaggia41b7292017-05-11 23:50:34 +0200312 /**
313 * Temporarily pauses/unpauses persisting of task snapshots.
314 *
315 * @param paused Whether task snapshot persisting should be paused.
316 */
317 void setPersisterPaused(boolean paused) {
318 mPersister.setPaused(paused);
319 }
320
Jorim Jaggi51304d72017-05-17 17:25:32 +0200321 /**
322 * Called when screen is being turned off.
323 */
324 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700325 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200326 listener.onScreenOff();
327 return;
328 }
329
330 // We can't take a snapshot when screen is off, so take a snapshot now!
331 mHandler.post(() -> {
332 try {
333 synchronized (mService.mWindowMap) {
334 mTmpTasks.clear();
335 mService.mRoot.forAllTasks(task -> {
336 if (task.isVisible()) {
337 mTmpTasks.add(task);
338 }
339 });
340 snapshotTasks(mTmpTasks);
341 }
342 } finally {
343 listener.onScreenOff();
344 }
345 });
346 }
347
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100348 void dump(PrintWriter pw, String prefix) {
349 mCache.dump(pw, prefix);
350 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800351}