blob: b8d0b8c096fe74d3c17e9ee6ee0ffb26ddffbdf7 [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 Jaggi8f4fe6e2017-03-14 18:21:40 +010020import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
21import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
22import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_RARELY;
23import static android.graphics.PixelFormat.RGBA_8888;
Jorim Jaggi02886a82016-12-06 09:10:06 -080024
25import android.annotation.Nullable;
Jorim Jaggi3d732602017-02-22 17:56:40 +010026import android.app.ActivityManager;
Jorim Jaggi02886a82016-12-06 09:10:06 -080027import android.app.ActivityManager.StackId;
Jorim Jaggie2c77f92016-12-29 14:57:22 +010028import android.app.ActivityManager.TaskSnapshot;
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010029import android.graphics.Canvas;
Jorim Jaggi02886a82016-12-06 09:10:06 -080030import android.graphics.GraphicBuffer;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010031import android.os.Environment;
Jorim Jaggi02886a82016-12-06 09:10:06 -080032import android.util.ArraySet;
33import android.view.WindowManagerPolicy.StartingSurface;
34
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010035import com.google.android.collect.Sets;
36
Jorim Jaggi02886a82016-12-06 09:10:06 -080037import com.android.internal.annotations.VisibleForTesting;
38
Jorim Jaggi10abe2f2017-01-03 16:44:46 +010039import java.io.PrintWriter;
40
Jorim Jaggi02886a82016-12-06 09:10:06 -080041/**
42 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
43 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
44 * like without any copying.
45 * <p>
46 * System applications may retrieve a snapshot to represent the current state of a task, and draw
47 * them in their own process.
48 * <p>
49 * When we task becomes visible again, we show a starting window with the snapshot as the content to
50 * make app transitions more responsive.
51 * <p>
52 * To access this class, acquire the global window manager lock.
53 */
54class TaskSnapshotController {
55
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +010056 /**
57 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
58 * used as the snapshot.
59 */
60 @VisibleForTesting
61 static final int SNAPSHOT_MODE_REAL = 0;
62
63 /**
64 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
65 * we should try to use the app theme to create a dummy representation of the app.
66 */
67 @VisibleForTesting
68 static final int SNAPSHOT_MODE_APP_THEME = 1;
69
70 /**
71 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
72 */
73 @VisibleForTesting
74 static final int SNAPSHOT_MODE_NONE = 2;
75
Jorim Jaggi02886a82016-12-06 09:10:06 -080076 private final WindowManagerService mService;
Jorim Jaggi02886a82016-12-06 09:10:06 -080077
Jorim Jaggi7361bab2017-01-16 17:17:58 +010078 private final TaskSnapshotCache mCache;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010079 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
80 Environment::getDataSystemCeDirectory);
81 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
Jorim Jaggi02886a82016-12-06 09:10:06 -080082 private final ArraySet<Task> mTmpTasks = new ArraySet<>();
83
84 TaskSnapshotController(WindowManagerService service) {
85 mService = service;
Jorim Jaggi7361bab2017-01-16 17:17:58 +010086 mCache = new TaskSnapshotCache(mService, mLoader);
Jorim Jaggi02886a82016-12-06 09:10:06 -080087 }
88
Jorim Jaggif9084ec2017-01-16 13:16:59 +010089 void systemReady() {
90 mPersister.start();
91 }
92
Jorim Jaggi02886a82016-12-06 09:10:06 -080093 void onTransitionStarting() {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010094 handleClosingApps(mService.mClosingApps);
95 }
96
Jorim Jaggi8b702ed2017-01-20 16:59:03 +010097 /**
98 * Called when the visibility of an app changes outside of the regular app transition flow.
99 */
100 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100101 if (!visible) {
102 handleClosingApps(Sets.newArraySet(appWindowToken));
103 }
104 }
105
106 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
Jorim Jaggi3d732602017-02-22 17:56:40 +0100107 if (!ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic()) {
108 return;
109 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800110
111 // We need to take a snapshot of the task if and only if all activities of the task are
112 // either closing or hidden.
Jorim Jaggi8b702ed2017-01-20 16:59:03 +0100113 getClosingTasks(closingApps, mTmpTasks);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800114 for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
115 final Task task = mTmpTasks.valueAt(i);
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100116 final int mode = getSnapshotMode(task);
117 final TaskSnapshot snapshot;
118 switch (mode) {
119 case SNAPSHOT_MODE_NONE:
120 continue;
121 case SNAPSHOT_MODE_APP_THEME:
122 snapshot = drawAppThemeSnapshot(task);
123 break;
124 case SNAPSHOT_MODE_REAL:
125 snapshot = snapshotTask(task);
126 break;
127 default:
128 snapshot = null;
129 break;
Jorim Jaggi02886a82016-12-06 09:10:06 -0800130 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100131 if (snapshot != null) {
132 mCache.putSnapshot(task, snapshot);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100133 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100134 if (task.getController() != null) {
135 task.getController().reportSnapshotChanged(snapshot);
136 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800137 }
138 }
139 }
140
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100141 /**
142 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
143 * MANAGER LOCK WHEN CALLING THIS METHOD!
144 */
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100145 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
146 boolean reducedResolution) {
147 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800148 }
149
150 /**
151 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
152 * MANAGER LOCK WHEN CALLING THIS METHOD!
153 */
154 StartingSurface createStartingSurface(AppWindowToken token,
155 GraphicBuffer snapshot) {
156 return TaskSnapshotSurface.create(mService, token, snapshot);
157 }
158
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100159 private TaskSnapshot snapshotTask(Task task) {
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100160 final AppWindowToken top = task.getTopChild();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800161 if (top == null) {
162 return null;
163 }
Jorim Jaggi6a7a8592017-01-12 00:44:33 +0100164 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
165 -1, -1, false, 1.0f, false, true);
166 if (buffer == null) {
Jorim Jaggi02886a82016-12-06 09:10:06 -0800167 return null;
168 }
Jorim Jaggie2c77f92016-12-29 14:57:22 +0100169 return new TaskSnapshot(buffer, top.getConfiguration().orientation,
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100170 top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800171 }
172
173 /**
174 * Retrieves all closing tasks based on the list of closing apps during an app transition.
175 */
176 @VisibleForTesting
177 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
178 outClosingTasks.clear();
179 for (int i = closingApps.size() - 1; i >= 0; i--) {
180 final AppWindowToken atoken = closingApps.valueAt(i);
Bryce Lee6d410262017-02-28 15:30:17 -0800181 final Task task = atoken.getTask();
Jorim Jaggi02886a82016-12-06 09:10:06 -0800182
183 // If the task of the app is not visible anymore, it means no other app in that task
184 // is opening. Thus, the task is closing.
Bryce Lee6d410262017-02-28 15:30:17 -0800185 if (task != null && !task.isVisible()) {
186 outClosingTasks.add(task);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800187 }
188 }
189 }
190
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100191 @VisibleForTesting
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100192 int getSnapshotMode(Task task) {
Jorim Jaggi0fe7ce962017-02-22 16:45:48 +0100193 final AppWindowToken topChild = task.getTopChild();
Jorim Jaggi8f4fe6e2017-03-14 18:21:40 +0100194 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
195 return SNAPSHOT_MODE_NONE;
196 } else if (topChild != null && topChild.shouldDisablePreviewScreenshots()) {
197 return SNAPSHOT_MODE_APP_THEME;
198 } else {
199 return SNAPSHOT_MODE_REAL;
200 }
201 }
202
203 /**
204 * If we are not allowed to take a real screenshot, this attempts to represent the app as best
205 * as possible by using the theme's window background.
206 */
207 private TaskSnapshot drawAppThemeSnapshot(Task task) {
208 final AppWindowToken topChild = task.getTopChild();
209 if (topChild == null) {
210 return null;
211 }
212 final WindowState mainWindow = topChild.findMainWindow();
213 if (mainWindow == null) {
214 return null;
215 }
216 final int color = task.getTaskDescription().getBackgroundColor();
217 final GraphicBuffer buffer = GraphicBuffer.create(mainWindow.getFrameLw().width(),
218 mainWindow.getFrameLw().height(),
219 RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_RARELY | USAGE_SW_READ_NEVER);
220 if (buffer == null) {
221 return null;
222 }
223 final Canvas c = buffer.lockCanvas();
224 c.drawColor(color);
225 buffer.unlockCanvasAndPost(c);
226 return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
227 mainWindow.mStableInsets, false /* reduced */, 1.0f /* scale */);
Jorim Jaggi02886a82016-12-06 09:10:06 -0800228 }
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100229
230 /**
231 * Called when an {@link AppWindowToken} has been removed.
232 */
233 void onAppRemoved(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100234 mCache.onAppRemoved(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100235 }
236
237 /**
238 * Called when the process of an {@link AppWindowToken} has died.
239 */
240 void onAppDied(AppWindowToken wtoken) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100241 mCache.onAppDied(wtoken);
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100242 }
243
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100244 void notifyTaskRemovedFromRecents(int taskId, int userId) {
Jorim Jaggi7361bab2017-01-16 17:17:58 +0100245 mCache.onTaskRemoved(taskId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100246 mPersister.onTaskRemovedFromRecents(taskId, userId);
247 }
248
249 /**
250 * See {@link TaskSnapshotPersister#removeObsoleteFiles}
251 */
252 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
253 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
254 }
255
Jorim Jaggi10abe2f2017-01-03 16:44:46 +0100256 void dump(PrintWriter pw, String prefix) {
257 mCache.dump(pw, prefix);
258 }
Jorim Jaggi02886a82016-12-06 09:10:06 -0800259}