blob: 212a0d70927af0fff365e2015b88e031b896a246 [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
Matthew Ngcb7ac672017-07-21 17:27:42 -070019import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
20import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
chaviwfbe47df2017-11-10 16:14:49 -080021import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
Winson Chung3e13ef82017-06-29 12:41:14 -070022import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24
Jorim Jaggi02886a82016-12-06 09:10:06 -080025import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010026import android.app.ActivityManager;
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;
chaviwfbe47df2017-11-10 16:14:49 -080038import android.view.SurfaceControl;
Winson Chung91092762017-05-24 14:08:48 -070039import android.view.ThreadedRenderer;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020040import android.view.WindowManager.LayoutParams;
Guang Zhu09486a32017-06-06 03:27:44 +000041
Jorim Jaggi02886a82016-12-06 09:10:06 -080042import com.android.internal.annotations.VisibleForTesting;
Adrian Roose99bc052017-11-20 17:55:31 +010043import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
44import com.android.server.policy.WindowManagerPolicy.StartingSurface;
Jorim Jaggid635a4a2017-05-03 15:21:26 +020045import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
Jorim Jaggi02886a82016-12-06 09:10:06 -080046
Adrian Roose99bc052017-11-20 17:55:31 +010047import com.google.android.collect.Sets;
48
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010049import java.io.PrintWriter;
50
Jorim Jaggi02886a82016-12-06 09:10:06 -080051/**
52 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
53 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
54 * like without any copying.
55 * <p>
56 * System applications may retrieve a snapshot to represent the current state of a task, and draw
57 * them in their own process.
58 * <p>
59 * When we task becomes visible again, we show a starting window with the snapshot as the content to
60 * make app transitions more responsive.
61 * <p>
62 * To access this class, acquire the global window manager lock.
63 */
64class TaskSnapshotController {
Winson Chung3e13ef82017-06-29 12:41:14 -070065 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
Jorim Jaggi02886a82016-12-06 09:10:06 -080066
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010067 /**
68 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
69 * used as the snapshot.
70 */
71 @VisibleForTesting
72 static final int SNAPSHOT_MODE_REAL = 0;
73
74 /**
75 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
76 * we should try to use the app theme to create a dummy representation of the app.
77 */
78 @VisibleForTesting
79 static final int SNAPSHOT_MODE_APP_THEME = 1;
80
81 /**
82 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
83 */
84 @VisibleForTesting
85 static final int SNAPSHOT_MODE_NONE = 2;
86
Jorim Jaggi02886a82016-12-06 09:10:06 -080087 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080088
Jorim Jaggi7361bab2017-01-16 17:17:58 +010089 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010090 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
91 Environment::getDataSystemCeDirectory);
92 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080093 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
Jorim Jaggi51304d72017-05-17 17:25:32 +020094 private final Handler mHandler = new Handler();
Jorim Jaggi02886a82016-12-06 09:10:06 -080095
chaviw23ee71c2017-12-18 11:29:41 -080096 private final Rect mTmpRect = new Rect();
97
Keyvan Amirie681cec2017-06-05 22:48:26 -070098 /**
99 * Flag indicating whether we are running on an Android TV device.
100 */
101 private final boolean mIsRunningOnTv;
102
Juho Haf5dd7cc2017-06-15 11:38:56 +0900103 /**
104 * Flag indicating whether we are running on an IoT device.
105 */
106 private final boolean mIsRunningOnIoT;
107
Matthew Ng95378192017-08-16 11:57:00 -0700108 /**
109 * Flag indicating whether we are running on an Android Wear device.
110 */
111 private final boolean mIsRunningOnWear;
112
Jorim Jaggi02886a82016-12-06 09:10:06 -0800113 TaskSnapshotController(WindowManagerService service) {
114 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100115 mCache = new TaskSnapshotCache(mService, mLoader);
Keyvan Amirie681cec2017-06-05 22:48:26 -0700116 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
117 PackageManager.FEATURE_LEANBACK);
Juho Haf5dd7cc2017-06-15 11:38:56 +0900118 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
119 PackageManager.FEATURE_EMBEDDED);
Matthew Ng95378192017-08-16 11:57:00 -0700120 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
121 PackageManager.FEATURE_WATCH);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800122 }
123
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100124 void systemReady() {
125 mPersister.start();
126 }
127
Jorim Jaggi02886a82016-12-06 09:10:06 -0800128 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100129 handleClosingApps(mService.mClosingApps);
130 }
131
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100132 /**
133 * Called when the visibility of an app changes outside of the regular app transition flow.
134 */
135 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100136 if (!visible) {
137 handleClosingApps(Sets.newArraySet(appWindowToken));
138 }
139 }
140
141 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700142 if (shouldDisableSnapshots()) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100143 return;
144 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800145
146 // We need to take a snapshot of the task if and only if all activities of the task are
147 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100148 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi51304d72017-05-17 17:25:32 +0200149 snapshotTasks(mTmpTasks);
150
151 }
152
153 private void snapshotTasks(ArraySet<Task> tasks) {
154 for (int i = tasks.size() - 1; i >= 0; i--) {
155 final Task task = tasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100156 final int mode = getSnapshotMode(task);
157 final TaskSnapshot snapshot;
158 switch (mode) {
159 case SNAPSHOT_MODE_NONE:
160 continue;
161 case SNAPSHOT_MODE_APP_THEME:
162 snapshot = drawAppThemeSnapshot(task);
163 break;
164 case SNAPSHOT_MODE_REAL:
165 snapshot = snapshotTask(task);
166 break;
167 default:
168 snapshot = null;
169 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800170 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100171 if (snapshot != null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700172 final GraphicBuffer buffer = snapshot.getSnapshot();
173 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
174 buffer.destroy();
175 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
176 + buffer.getHeight());
177 } else {
178 mCache.putSnapshot(task, snapshot);
179 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
180 if (task.getController() != null) {
181 task.getController().reportSnapshotChanged(snapshot);
182 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100183 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800184 }
185 }
186 }
187
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100188 /**
189 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
190 * MANAGER LOCK WHEN CALLING THIS METHOD!
191 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100192 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
193 boolean reducedResolution) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700194 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
195 || DISABLE_FULL_SIZED_BITMAPS);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800196 }
197
198 /**
199 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
200 * MANAGER LOCK WHEN CALLING THIS METHOD!
201 */
202 StartingSurface createStartingSurface(AppWindowToken token,
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200203 TaskSnapshot snapshot) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800204 return TaskSnapshotSurface.create(mService, token, snapshot);
205 }
206
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100207 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100208 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800209 if (top == null) {
210 return null;
211 }
Jorim Jaggidd5986e2017-04-11 15:50:19 -0700212 final WindowState mainWindow = top.findMainWindow();
213 if (mainWindow == null) {
214 return null;
215 }
chaviwfbe47df2017-11-10 16:14:49 -0800216 if (!mService.mPolicy.isScreenOn()) {
217 if (DEBUG_SCREENSHOT) {
218 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
219 }
220 return null;
221 }
222 if (task.getSurfaceControl() == null) {
223 return null;
224 }
225
chaviw7f1fa992018-01-10 13:52:12 -0800226 if (top.hasCommittedReparentToAnimationLeash()) {
227 if (DEBUG_SCREENSHOT) {
228 Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + top);
229 }
230 return null;
231 }
232
233 final boolean hasVisibleChild = top.forAllWindows(
234 // Ensure at least one window for the top app is visible before attempting to take
235 // a screenshot. Visible here means that the WSA surface is shown and has an alpha
236 // greater than 0.
237 ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown()
238 && ws.mWinAnimator.mLastAlpha > 0f, true);
239
240 if (!hasVisibleChild) {
241 if (DEBUG_SCREENSHOT) {
242 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
243 }
244 return null;
245 }
246
Matthew Ngcb7ac672017-07-21 17:27:42 -0700247 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
248 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
chaviw23ee71c2017-12-18 11:29:41 -0800249 task.getBounds(mTmpRect);
250 mTmpRect.offsetTo(0, 0);
chaviwfbe47df2017-11-10 16:14:49 -0800251
Chavi Weingartend7ec64c2017-11-30 01:52:01 +0000252 final GraphicBuffer buffer = SurfaceControl.captureLayers(
chaviw23ee71c2017-12-18 11:29:41 -0800253 task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
chaviwfbe47df2017-11-10 16:14:49 -0800254
Juho Had864b442017-06-12 20:15:31 +0900255 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
chaviwfbe47df2017-11-10 16:14:49 -0800256 if (DEBUG_SCREENSHOT) {
chaviw7f1fa992018-01-10 13:52:12 -0800257 Slog.w(TAG_WM, "Failed to take screenshot for " + task);
chaviwfbe47df2017-11-10 16:14:49 -0800258 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800259 return null;
260 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100261 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Adrian Roos98a146d2017-11-29 16:39:44 +0100262 getInsetsFromTaskBounds(mainWindow, task),
Matthew Ngcb7ac672017-07-21 17:27:42 -0700263 isLowRamDevice /* reduced */, scaleFraction /* scale */);
Jorim Jaggi30d64f32017-04-07 16:33:17 +0200264 }
265
Keyvan Amirie681cec2017-06-05 22:48:26 -0700266 private boolean shouldDisableSnapshots() {
Jorim Jaggie7d2b852017-08-28 17:55:15 +0200267 return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
Keyvan Amirie681cec2017-06-05 22:48:26 -0700268 }
269
Adrian Roos98a146d2017-11-29 16:39:44 +0100270 private Rect getInsetsFromTaskBounds(WindowState state, Task task) {
271 final Rect r = new Rect();
272 r.set(state.getContentFrameLw());
273 r.intersectUnchecked(state.getStableFrameLw());
274
275 final Rect taskBounds = task.getBounds();
276
277 r.set(Math.max(0, r.left - taskBounds.left),
278 Math.max(0, r.top - taskBounds.top),
279 Math.max(0, taskBounds.right - r.right),
280 Math.max(0, taskBounds.bottom - r.bottom));
281 return r;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800282 }
283
284 /**
285 * Retrieves all closing tasks based on the list of closing apps during an app transition.
286 */
287 @VisibleForTesting
288 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
289 outClosingTasks.clear();
290 for (int i = closingApps.size() - 1; i >= 0; i--) {
291 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800292 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800293
294 // If the task of the app is not visible anymore, it means no other app in that task
295 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800296 if (task != null && !task.isVisible()) {
297 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800298 }
299 }
300 }
301
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100302 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100303 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100304 final AppWindowToken topChild = task.getTopChild();
Wale Ogunwale68278562017-09-23 17:13:55 -0700305 if (!task.isActivityTypeStandardOrUndefined()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100306 return SNAPSHOT_MODE_NONE;
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200307 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100308 return SNAPSHOT_MODE_APP_THEME;
309 } else {
310 return SNAPSHOT_MODE_REAL;
311 }
312 }
313
314 /**
315 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
316 * as possible by using the theme's window background.
317 */
318 private TaskSnapshot drawAppThemeSnapshot(Task task) {
319 final AppWindowToken topChild = task.getTopChild();
320 if (topChild == null) {
321 return null;
322 }
323 final WindowState mainWindow = topChild.findMainWindow();
324 if (mainWindow == null) {
325 return null;
326 }
327 final int color = task.getTaskDescription().getBackgroundColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200328 final int statusBarColor = task.getTaskDescription().getStatusBarColor();
329 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200330 final LayoutParams attrs = mainWindow.getAttrs();
331 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
332 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
Winson Chung91092762017-05-24 14:08:48 -0700333 final int width = mainWindow.getFrameLw().width();
334 final int height = mainWindow.getFrameLw().height();
335
336 final RenderNode node = RenderNode.create("TaskSnapshotController", null);
337 node.setLeftTopRightBottom(0, 0, width, height);
338 node.setClipToBounds(false);
339 final DisplayListCanvas c = node.start(width, height);
340 c.drawColor(color);
Jorim Jaggid635a4a2017-05-03 15:21:26 +0200341 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
342 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
Winson Chung91092762017-05-24 14:08:48 -0700343 node.end(c);
344 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
Winson Chung6267f992017-10-06 14:01:10 -0700345 if (hwBitmap == null) {
346 return null;
347 }
Jorim Jaggi6aead1c2017-05-23 15:07:44 +0200348 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
349 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
Matthew Ng30a7f7c2017-09-18 16:29:46 -0700350 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800351 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100352
353 /**
354 * Called when an {@link AppWindowToken} has been removed.
355 */
356 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100357 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100358 }
359
360 /**
361 * Called when the process of an {@link AppWindowToken} has died.
362 */
363 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100364 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100365 }
366
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100367 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100368 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100369 mPersister.onTaskRemovedFromRecents(taskId, userId);
370 }
371
372 /**
373 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
374 */
375 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
376 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
377 }
378
Jorim Jaggia41b7292017-05-11 23:50:34 +0200379 /**
380 * Temporarily pauses/unpauses persisting of task snapshots.
381 *
382 * @param paused Whether task snapshot persisting should be paused.
383 */
384 void setPersisterPaused(boolean paused) {
385 mPersister.setPaused(paused);
386 }
387
Jorim Jaggi51304d72017-05-17 17:25:32 +0200388 /**
389 * Called when screen is being turned off.
390 */
391 void screenTurningOff(ScreenOffListener listener) {
Keyvan Amirie681cec2017-06-05 22:48:26 -0700392 if (shouldDisableSnapshots()) {
Jorim Jaggi51304d72017-05-17 17:25:32 +0200393 listener.onScreenOff();
394 return;
395 }
396
397 // We can't take a snapshot when screen is off, so take a snapshot now!
398 mHandler.post(() -> {
399 try {
400 synchronized (mService.mWindowMap) {
401 mTmpTasks.clear();
402 mService.mRoot.forAllTasks(task -> {
403 if (task.isVisible()) {
404 mTmpTasks.add(task);
405 }
406 });
407 snapshotTasks(mTmpTasks);
408 }
409 } finally {
410 listener.onScreenOff();
411 }
412 });
413 }
414
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100415 void dump(PrintWriter pw, String prefix) {
416 mCache.dump(pw, prefix);
417 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800418}