blob: ecf9067b55c922297be59d9ffd114c85d1f92163 [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
Matthew Ngcb7ac672017-07-21 17:27:42 -070021import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
22import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
Winson Chung3e13ef82017-06-29 12:41:14 -070023import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25
Jorim Jaggi02886a82016-12-06 09:10:06 -080026import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010027import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080028import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010029import android.app.ActivityManager.TaskSnapshot;
Keyvan Amirie681cec2017-06-05 22:48:26 -070030import android.content.pm.PackageManager;
Jorim Jaggi6aead1c2017-05-23 15:07:44 +020031import android.graphics.Bitmap;
Jorim Jaggi02886a82016-12-06 09:10:06 -080032import android.graphics.GraphicBuffer;
Jorim Jaggi30d64f32017-04-07 16:33:17 +020033import android.graphics.Rect;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010034import android.os.Environment;
Jorim Jaggi51304d72017-05-17 17:25:32 +020035import android.os.Handler;
Jorim Jaggi02886a82016-12-06 09:10:06 -080036import android.util.ArraySet;
Winson Chung3e13ef82017-06-29 12:41:14 -070037import android.util.Slog;
Winson Chung91092762017-05-24 14:08:48 -070038import android.view.DisplayListCanvas;
39import android.view.RenderNode;
40import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020041import android.view.WindowManager.LayoutParams;
Jorim Jaggi51304d72017-05-17 17:25:32 +020042import android.view.WindowManagerPolicy.ScreenOffListener;
Jorim Jaggi02886a82016-12-06 09:10:06 -080043import android.view.WindowManagerPolicy.StartingSurface;
44
Guang Zhu09486a32017-06-06 03:27:44 +000045import com.google.android.collect.Sets;
46
Jorim Jaggi02886a82016-12-06 09:10:06 -080047import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020048import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080049
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010050import java.io.PrintWriter;
51
Jorim Jaggi02886a82016-12-06 09:10:06 -080052/**
53 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
54 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
55 * like without any copying.
56 * <p>
57 * System applications may retrieve a snapshot to represent the current state of a task, and draw
58 * them in their own process.
59 * <p>
60 * When we task becomes visible again, we show a starting window with the snapshot as the content to
61 * make app transitions more responsive.
62 * <p>
63 * To access this class, acquire the global window manager lock.
64 */
65class TaskSnapshotController {
Winson Chung3e13ef82017-06-29 12:41:14 -070066 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
Jorim Jaggi02886a82016-12-06 09:10:06 -080067
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010068 /**
69 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
70 * used as the snapshot.
71 */
72 @VisibleForTesting
73 static final int SNAPSHOT_MODE_REAL = 0;
74
75 /**
76 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
77 * we should try to use the app theme to create a dummy representation of the app.
78 */
79 @VisibleForTesting
80 static final int SNAPSHOT_MODE_APP_THEME = 1;
81
82 /**
83 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
84 */
85 @VisibleForTesting
86 static final int SNAPSHOT_MODE_NONE = 2;
87
Jorim Jaggi02886a82016-12-06 09:10:06 -080088 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080089
Jorim Jaggi7361bab2017-01-16 17:17:58 +010090 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010091 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
92 Environment::getDataSystemCeDirectory);
93 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080094 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020095 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080096
Keyvan Amirie681cec2017-06-05 22:48:26 -070097 /**
98 * Flag indicating whether we are running on an Android TV device.
99 */
100 private final boolean mIsRunningOnTv;
101
Juho Haf5dd7cc2017-06-15 11:38:56 +0900102 /**
103 * Flag indicating whether we are running on an IoT device.
104 */
105 private final boolean mIsRunningOnIoT;
106
Matthew Ng95378192017-08-16 11:57:00 -0700107 /**
108 * Flag indicating whether we are running on an Android Wear device.
109 */
110 private final boolean mIsRunningOnWear;
111
Jorim Jaggi02886a82016-12-06 09:10:06 -0800112 TaskSnapshotController(WindowManagerService service) {
113 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100114 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700115 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
116 PackageManager.FEATURE_LEANBACK);
Juho Haf5dd7cc2017-06-15 11:38:56 +0900117 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
118 PackageManager.FEATURE_EMBEDDED);
Matthew Ng95378192017-08-16 11:57:00 -0700119 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
120 PackageManager.FEATURE_WATCH);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800121 }
122
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100123 void systemReady() {
124 mPersister.start();
125 }
126
Jorim Jaggi02886a82016-12-06 09:10:06 -0800127 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100128 handleClosingApps(mService.mClosingApps);
129 }
130
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100131 /**
132 * Called when the visibility of an app changes outside of the regular app transition flow.
133 */
134 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100135 if (!visible) {
136 handleClosingApps(Sets.newArraySet(appWindowToken));
137 }
138 }
139
140 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700141 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100142 return;
143 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800144
145 // We need to take a snapshot of the task if and only if all activities of the task are
146 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100147 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200148 snapshotTasks(mTmpTasks);
149
150 }
151
152 private void snapshotTasks(ArraySet<Task> tasks) {
153 for (int i = tasks.size() - 1; i >= 0; i--) {
154 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100155 final int mode = getSnapshotMode(task);
156 final TaskSnapshot snapshot;
157 switch (mode) {
158 case SNAPSHOT_MODE_NONE:
159 continue;
160 case SNAPSHOT_MODE_APP_THEME:
161 snapshot = drawAppThemeSnapshot(task);
162 break;
163 case SNAPSHOT_MODE_REAL:
164 snapshot = snapshotTask(task);
165 break;
166 default:
167 snapshot = null;
168 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800169 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100170 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700171 final GraphicBuffer buffer = snapshot.getSnapshot();
172 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
173 buffer.destroy();
174 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
175 + buffer.getHeight());
176 } else {
177 mCache.putSnapshot(task, snapshot);
178 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
179 if (task.getController() != null) {
180 task.getController().reportSnapshotChanged(snapshot);
181 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100182 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800183 }
184 }
185 }
186
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100187 /**
188 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
189 * MANAGER LOCK WHEN CALLING THIS METHOD!
190 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100191 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
192 boolean reducedResolution) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700193 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
194 || DISABLE_FULL_SIZED_BITMAPS);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800195 }
196
197 /**
198 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
199 * MANAGER LOCK WHEN CALLING THIS METHOD!
200 */
201 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200202 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800203 return TaskSnapshotSurface.create(mService, token, snapshot);
204 }
205
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100206 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100207 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800208 if (top == null) {
209 return null;
210 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700211 final WindowState mainWindow = top.findMainWindow();
212 if (mainWindow == null) {
213 return null;
214 }
Matthew Ngcb7ac672017-07-21 17:27:42 -0700215 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
216 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100217 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
Matthew Ngcb7ac672017-07-21 17:27:42 -0700218 -1, -1, false, scaleFraction, false, true);
Juho Had864b442017-06-12 20:15:31 +0900219 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800220 return null;
221 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100222 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Matthew Ngcb7ac672017-07-21 17:27:42 -0700223 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets),
224 isLowRamDevice /* reduced */, scaleFraction /* scale */);
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200225 }
226
Keyvan Amirie681cec2017-06-05 22:48:26 -0700227 private boolean shouldDisableSnapshots() {
Matthew Ng95378192017-08-16 11:57:00 -0700228 return !ENABLE_TASK_SNAPSHOTS || mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
Keyvan Amirie681cec2017-06-05 22:48:26 -0700229 }
230
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200231 private Rect minRect(Rect rect1, Rect rect2) {
232 return new Rect(Math.min(rect1.left, rect2.left),
233 Math.min(rect1.top, rect2.top),
234 Math.min(rect1.right, rect2.right),
235 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800236 }
237
238 /**
239 * Retrieves all closing tasks based on the list of closing apps during an app transition.
240 */
241 @VisibleForTesting
242 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
243 outClosingTasks.clear();
244 for (int i = closingApps.size() - 1; i >= 0; i--) {
245 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800246 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800247
248 // If the task of the app is not visible anymore, it means no other app in that task
249 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800250 if (task != null && !task.isVisible()) {
251 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800252 }
253 }
254 }
255
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100256 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100257 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100258 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100259 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
260 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200261 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100262 return SNAPSHOT_MODE_APP_THEME;
263 } else {
264 return SNAPSHOT_MODE_REAL;
265 }
266 }
267
268 /**
269 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
270 * as possible by using the theme's window background.
271 */
272 private TaskSnapshot drawAppThemeSnapshot(Task task) {
273 final AppWindowToken topChild = task.getTopChild();
274 if (topChild == null) {
275 return null;
276 }
277 final WindowState mainWindow = topChild.findMainWindow();
278 if (mainWindow == null) {
279 return null;
280 }
281 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200282 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
283 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200284 final LayoutParams attrs = mainWindow.getAttrs();
285 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
286 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700287 final int width = mainWindow.getFrameLw().width();
288 final int height = mainWindow.getFrameLw().height();
289
290 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
291 node.setLeftTopRightBottom(0, 0, width, height);
292 node.setClipToBounds(false);
293 final DisplayListCanvas c = node.start(width, height);
294 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200295 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
296 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700297 node.end(c);
298 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200299
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200300 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
301 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
302 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800303 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100304
305 /**
306 * Called when an {@link AppWindowToken} has been removed.
307 */
308 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100309 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100310 }
311
312 /**
313 * Called when the process of an {@link AppWindowToken} has died.
314 */
315 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100316 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100317 }
318
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100319 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100320 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100321 mPersister.onTaskRemovedFromRecents(taskId, userId);
322 }
323
324 /**
325 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
326 */
327 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
328 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
329 }
330
Jorim Jaggia41b7292017-05-11 23:50:34 +0200331 /**
332 * Temporarily pauses/unpauses persisting of task snapshots.
333 *
334 * @param paused Whether task snapshot persisting should be paused.
335 */
336 void setPersisterPaused(boolean paused) {
337 mPersister.setPaused(paused);
338 }
339
Jorim Jaggi51304d72017-05-17 17:25:32 +0200340 /**
341 * Called when screen is being turned off.
342 */
343 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700344 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200345 listener.onScreenOff();
346 return;
347 }
348
349 // We can't take a snapshot when screen is off, so take a snapshot now!
350 mHandler.post(() -> {
351 try {
352 synchronized (mService.mWindowMap) {
353 mTmpTasks.clear();
354 mService.mRoot.forAllTasks(task -> {
355 if (task.isVisible()) {
356 mTmpTasks.add(task);
357 }
358 });
359 snapshotTasks(mTmpTasks);
360 }
361 } finally {
362 listener.onScreenOff();
363 }
364 });
365 }
366
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100367 void dump(PrintWriter pw, String prefix) {
368 mCache.dump(pw, prefix);
369 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800370}