blob: 4e4d42dea0a6047ec0a635cd01fa3b5797bc6401 [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
Juho Haf5dd7cc2017-06-15 11:38:56 +0900100 /**
101 * Flag indicating whether we are running on an IoT device.
102 */
103 private final boolean mIsRunningOnIoT;
104
Matthew Ng95378192017-08-16 11:57:00 -0700105 /**
106 * Flag indicating whether we are running on an Android Wear device.
107 */
108 private final boolean mIsRunningOnWear;
109
Jorim Jaggi02886a82016-12-06 09:10:06 -0800110 TaskSnapshotController(WindowManagerService service) {
111 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100112 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700113 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
114 PackageManager.FEATURE_LEANBACK);
Juho Haf5dd7cc2017-06-15 11:38:56 +0900115 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
116 PackageManager.FEATURE_EMBEDDED);
Matthew Ng95378192017-08-16 11:57:00 -0700117 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
118 PackageManager.FEATURE_WATCH);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800119 }
120
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100121 void systemReady() {
122 mPersister.start();
123 }
124
Jorim Jaggi02886a82016-12-06 09:10:06 -0800125 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100126 handleClosingApps(mService.mClosingApps);
127 }
128
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100129 /**
130 * Called when the visibility of an app changes outside of the regular app transition flow.
131 */
132 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100133 if (!visible) {
134 handleClosingApps(Sets.newArraySet(appWindowToken));
135 }
136 }
137
138 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700139 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100140 return;
141 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800142
143 // We need to take a snapshot of the task if and only if all activities of the task are
144 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100145 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200146 snapshotTasks(mTmpTasks);
147
148 }
149
150 private void snapshotTasks(ArraySet<Task> tasks) {
151 for (int i = tasks.size() - 1; i >= 0; i--) {
152 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100153 final int mode = getSnapshotMode(task);
154 final TaskSnapshot snapshot;
155 switch (mode) {
156 case SNAPSHOT_MODE_NONE:
157 continue;
158 case SNAPSHOT_MODE_APP_THEME:
159 snapshot = drawAppThemeSnapshot(task);
160 break;
161 case SNAPSHOT_MODE_REAL:
162 snapshot = snapshotTask(task);
163 break;
164 default:
165 snapshot = null;
166 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800167 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100168 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700169 final GraphicBuffer buffer = snapshot.getSnapshot();
170 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
171 buffer.destroy();
172 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
173 + buffer.getHeight());
174 } else {
175 mCache.putSnapshot(task, snapshot);
176 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
177 if (task.getController() != null) {
178 task.getController().reportSnapshotChanged(snapshot);
179 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100180 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800181 }
182 }
183 }
184
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100185 /**
186 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
187 * MANAGER LOCK WHEN CALLING THIS METHOD!
188 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100189 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
190 boolean reducedResolution) {
191 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800192 }
193
194 /**
195 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
196 * MANAGER LOCK WHEN CALLING THIS METHOD!
197 */
198 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200199 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800200 return TaskSnapshotSurface.create(mService, token, snapshot);
201 }
202
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100203 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100204 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800205 if (top == null) {
206 return null;
207 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700208 final WindowState mainWindow = top.findMainWindow();
209 if (mainWindow == null) {
210 return null;
211 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100212 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
213 -1, -1, false, 1.0f, false, true);
214 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800215 return null;
216 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100217 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200218 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
219 1f /* scale */);
220 }
221
Keyvan Amirie681cec2017-06-05 22:48:26 -0700222 private boolean shouldDisableSnapshots() {
Matthew Ng95378192017-08-16 11:57:00 -0700223 return !ENABLE_TASK_SNAPSHOTS || mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
Keyvan Amirie681cec2017-06-05 22:48:26 -0700224 }
225
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200226 private Rect minRect(Rect rect1, Rect rect2) {
227 return new Rect(Math.min(rect1.left, rect2.left),
228 Math.min(rect1.top, rect2.top),
229 Math.min(rect1.right, rect2.right),
230 Math.min(rect1.bottom, rect2.bottom));
Jorim Jaggi02886a82016-12-06 09:10:06 -0800231 }
232
233 /**
234 * Retrieves all closing tasks based on the list of closing apps during an app transition.
235 */
236 @VisibleForTesting
237 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
238 outClosingTasks.clear();
239 for (int i = closingApps.size() - 1; i >= 0; i--) {
240 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800241 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800242
243 // If the task of the app is not visible anymore, it means no other app in that task
244 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800245 if (task != null && !task.isVisible()) {
246 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800247 }
248 }
249 }
250
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100251 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100252 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100253 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100254 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
255 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200256 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100257 return SNAPSHOT_MODE_APP_THEME;
258 } else {
259 return SNAPSHOT_MODE_REAL;
260 }
261 }
262
263 /**
264 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
265 * as possible by using the theme's window background.
266 */
267 private TaskSnapshot drawAppThemeSnapshot(Task task) {
268 final AppWindowToken topChild = task.getTopChild();
269 if (topChild == null) {
270 return null;
271 }
272 final WindowState mainWindow = topChild.findMainWindow();
273 if (mainWindow == null) {
274 return null;
275 }
276 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200277 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
278 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200279 final LayoutParams attrs = mainWindow.getAttrs();
280 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
281 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700282 final int width = mainWindow.getFrameLw().width();
283 final int height = mainWindow.getFrameLw().height();
284
285 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
286 node.setLeftTopRightBottom(0, 0, width, height);
287 node.setClipToBounds(false);
288 final DisplayListCanvas c = node.start(width, height);
289 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200290 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
291 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700292 node.end(c);
293 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200294
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200295 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
296 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
297 false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800298 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100299
300 /**
301 * Called when an {@link AppWindowToken} has been removed.
302 */
303 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100304 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100305 }
306
307 /**
308 * Called when the process of an {@link AppWindowToken} has died.
309 */
310 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100311 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100312 }
313
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100314 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100315 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100316 mPersister.onTaskRemovedFromRecents(taskId, userId);
317 }
318
319 /**
320 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
321 */
322 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
323 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
324 }
325
Jorim Jaggia41b7292017-05-11 23:50:34 +0200326 /**
327 * Temporarily pauses/unpauses persisting of task snapshots.
328 *
329 * @param paused Whether task snapshot persisting should be paused.
330 */
331 void setPersisterPaused(boolean paused) {
332 mPersister.setPaused(paused);
333 }
334
Jorim Jaggi51304d72017-05-17 17:25:32 +0200335 /**
336 * Called when screen is being turned off.
337 */
338 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700339 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200340 listener.onScreenOff();
341 return;
342 }
343
344 // We can't take a snapshot when screen is off, so take a snapshot now!
345 mHandler.post(() -> {
346 try {
347 synchronized (mService.mWindowMap) {
348 mTmpTasks.clear();
349 mService.mRoot.forAllTasks(task -> {
350 if (task.isVisible()) {
351 mTmpTasks.add(task);
352 }
353 });
354 snapshotTasks(mTmpTasks);
355 }
356 } finally {
357 listener.onScreenOff();
358 }
359 });
360 }
361
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100362 void dump(PrintWriter pw, String prefix) {
363 mCache.dump(pw, prefix);
364 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800365}