• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.wm;
18 
19 import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
20 
21 import android.annotation.Nullable;
22 import android.app.ActivityManager;
23 import android.app.ActivityManager.StackId;
24 import android.app.ActivityManager.TaskSnapshot;
25 import android.content.pm.PackageManager;
26 import android.graphics.Bitmap;
27 import android.graphics.GraphicBuffer;
28 import android.graphics.Rect;
29 import android.os.Environment;
30 import android.os.Handler;
31 import android.util.ArraySet;
32 import android.view.DisplayListCanvas;
33 import android.view.RenderNode;
34 import android.view.ThreadedRenderer;
35 import android.view.WindowManager.LayoutParams;
36 import android.view.WindowManagerPolicy.ScreenOffListener;
37 import android.view.WindowManagerPolicy.StartingSurface;
38 
39 import com.google.android.collect.Sets;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
43 
44 import java.io.PrintWriter;
45 
46 /**
47  * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
48  * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
49  * like without any copying.
50  * <p>
51  * System applications may retrieve a snapshot to represent the current state of a task, and draw
52  * them in their own process.
53  * <p>
54  * When we task becomes visible again, we show a starting window with the snapshot as the content to
55  * make app transitions more responsive.
56  * <p>
57  * To access this class, acquire the global window manager lock.
58  */
59 class TaskSnapshotController {
60 
61     /**
62      * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
63      * used as the snapshot.
64      */
65     @VisibleForTesting
66     static final int SNAPSHOT_MODE_REAL = 0;
67 
68     /**
69      * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
70      * we should try to use the app theme to create a dummy representation of the app.
71      */
72     @VisibleForTesting
73     static final int SNAPSHOT_MODE_APP_THEME = 1;
74 
75     /**
76      * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
77      */
78     @VisibleForTesting
79     static final int SNAPSHOT_MODE_NONE = 2;
80 
81     private final WindowManagerService mService;
82 
83     private final TaskSnapshotCache mCache;
84     private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
85             Environment::getDataSystemCeDirectory);
86     private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
87     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
88     private final Handler mHandler = new Handler();
89 
90     /**
91      * Flag indicating whether we are running on an Android TV device.
92      */
93     private final boolean mIsRunningOnTv;
94 
TaskSnapshotController(WindowManagerService service)95     TaskSnapshotController(WindowManagerService service) {
96         mService = service;
97         mCache = new TaskSnapshotCache(mService, mLoader);
98         mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
99                 PackageManager.FEATURE_LEANBACK);
100     }
101 
systemReady()102     void systemReady() {
103         mPersister.start();
104     }
105 
onTransitionStarting()106     void onTransitionStarting() {
107         handleClosingApps(mService.mClosingApps);
108     }
109 
110     /**
111      * Called when the visibility of an app changes outside of the regular app transition flow.
112      */
notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible)113     void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
114         if (!visible) {
115             handleClosingApps(Sets.newArraySet(appWindowToken));
116         }
117     }
118 
handleClosingApps(ArraySet<AppWindowToken> closingApps)119     private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
120         if (shouldDisableSnapshots()) {
121             return;
122         }
123 
124         // We need to take a snapshot of the task if and only if all activities of the task are
125         // either closing or hidden.
126         getClosingTasks(closingApps, mTmpTasks);
127         snapshotTasks(mTmpTasks);
128 
129     }
130 
snapshotTasks(ArraySet<Task> tasks)131     private void snapshotTasks(ArraySet<Task> tasks) {
132         for (int i = tasks.size() - 1; i >= 0; i--) {
133             final Task task = tasks.valueAt(i);
134             final int mode = getSnapshotMode(task);
135             final TaskSnapshot snapshot;
136             switch (mode) {
137                 case SNAPSHOT_MODE_NONE:
138                     continue;
139                 case SNAPSHOT_MODE_APP_THEME:
140                     snapshot = drawAppThemeSnapshot(task);
141                     break;
142                 case SNAPSHOT_MODE_REAL:
143                     snapshot = snapshotTask(task);
144                     break;
145                 default:
146                     snapshot = null;
147                     break;
148             }
149             if (snapshot != null) {
150                 mCache.putSnapshot(task, snapshot);
151                 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
152                 if (task.getController() != null) {
153                     task.getController().reportSnapshotChanged(snapshot);
154                 }
155             }
156         }
157     }
158 
159     /**
160      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
161      * MANAGER LOCK WHEN CALLING THIS METHOD!
162      */
getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean reducedResolution)163     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
164             boolean reducedResolution) {
165         return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
166     }
167 
168     /**
169      * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
170      * MANAGER LOCK WHEN CALLING THIS METHOD!
171      */
createStartingSurface(AppWindowToken token, TaskSnapshot snapshot)172     StartingSurface createStartingSurface(AppWindowToken token,
173             TaskSnapshot snapshot) {
174         return TaskSnapshotSurface.create(mService, token, snapshot);
175     }
176 
snapshotTask(Task task)177     private TaskSnapshot snapshotTask(Task task) {
178         final AppWindowToken top = task.getTopChild();
179         if (top == null) {
180             return null;
181         }
182         final WindowState mainWindow = top.findMainWindow();
183         if (mainWindow == null) {
184             return null;
185         }
186         final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
187                 -1, -1, false, 1.0f, false, true);
188         if (buffer == null) {
189             return null;
190         }
191         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
192                 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), false /* reduced */,
193                 1f /* scale */);
194     }
195 
shouldDisableSnapshots()196     private boolean shouldDisableSnapshots() {
197         return !ENABLE_TASK_SNAPSHOTS || ActivityManager.isLowRamDeviceStatic() || mIsRunningOnTv;
198     }
199 
minRect(Rect rect1, Rect rect2)200     private Rect minRect(Rect rect1, Rect rect2) {
201         return new Rect(Math.min(rect1.left, rect2.left),
202                 Math.min(rect1.top, rect2.top),
203                 Math.min(rect1.right, rect2.right),
204                 Math.min(rect1.bottom, rect2.bottom));
205     }
206 
207     /**
208      * Retrieves all closing tasks based on the list of closing apps during an app transition.
209      */
210     @VisibleForTesting
getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks)211     void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
212         outClosingTasks.clear();
213         for (int i = closingApps.size() - 1; i >= 0; i--) {
214             final AppWindowToken atoken = closingApps.valueAt(i);
215             final Task task = atoken.getTask();
216 
217             // If the task of the app is not visible anymore, it means no other app in that task
218             // is opening. Thus, the task is closing.
219             if (task != null && !task.isVisible()) {
220                 outClosingTasks.add(task);
221             }
222         }
223     }
224 
225     @VisibleForTesting
getSnapshotMode(Task task)226     int getSnapshotMode(Task task) {
227         final AppWindowToken topChild = task.getTopChild();
228         if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
229             return SNAPSHOT_MODE_NONE;
230         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
231             return SNAPSHOT_MODE_APP_THEME;
232         } else {
233             return SNAPSHOT_MODE_REAL;
234         }
235     }
236 
237     /**
238      * If we are not allowed to take a real screenshot, this attempts to represent the app as best
239      * as possible by using the theme's window background.
240      */
drawAppThemeSnapshot(Task task)241     private TaskSnapshot drawAppThemeSnapshot(Task task) {
242         final AppWindowToken topChild = task.getTopChild();
243         if (topChild == null) {
244             return null;
245         }
246         final WindowState mainWindow = topChild.findMainWindow();
247         if (mainWindow == null) {
248             return null;
249         }
250         final int color = task.getTaskDescription().getBackgroundColor();
251         final int statusBarColor = task.getTaskDescription().getStatusBarColor();
252         final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
253         final LayoutParams attrs = mainWindow.getAttrs();
254         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
255                 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
256         final int width = mainWindow.getFrameLw().width();
257         final int height = mainWindow.getFrameLw().height();
258 
259         final RenderNode node = RenderNode.create("TaskSnapshotController", null);
260         node.setLeftTopRightBottom(0, 0, width, height);
261         node.setClipToBounds(false);
262         final DisplayListCanvas c = node.start(width, height);
263         c.drawColor(color);
264         decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
265         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
266         node.end(c);
267         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
268 
269         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
270                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
271                 false /* reduced */, 1.0f /* scale */);
272     }
273 
274     /**
275      * Called when an {@link AppWindowToken} has been removed.
276      */
onAppRemoved(AppWindowToken wtoken)277     void onAppRemoved(AppWindowToken wtoken) {
278         mCache.onAppRemoved(wtoken);
279     }
280 
281     /**
282      * Called when the process of an {@link AppWindowToken} has died.
283      */
onAppDied(AppWindowToken wtoken)284     void onAppDied(AppWindowToken wtoken) {
285         mCache.onAppDied(wtoken);
286     }
287 
notifyTaskRemovedFromRecents(int taskId, int userId)288     void notifyTaskRemovedFromRecents(int taskId, int userId) {
289         mCache.onTaskRemoved(taskId);
290         mPersister.onTaskRemovedFromRecents(taskId, userId);
291     }
292 
293     /**
294      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
295      */
removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)296     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
297         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
298     }
299 
300     /**
301      * Temporarily pauses/unpauses persisting of task snapshots.
302      *
303      * @param paused Whether task snapshot persisting should be paused.
304      */
setPersisterPaused(boolean paused)305     void setPersisterPaused(boolean paused) {
306         mPersister.setPaused(paused);
307     }
308 
309     /**
310      * Called when screen is being turned off.
311      */
screenTurningOff(ScreenOffListener listener)312     void screenTurningOff(ScreenOffListener listener) {
313         if (shouldDisableSnapshots()) {
314             listener.onScreenOff();
315             return;
316         }
317 
318         // We can't take a snapshot when screen is off, so take a snapshot now!
319         mHandler.post(() -> {
320             try {
321                 synchronized (mService.mWindowMap) {
322                     mTmpTasks.clear();
323                     mService.mRoot.forAllTasks(task -> {
324                         if (task.isVisible()) {
325                             mTmpTasks.add(task);
326                         }
327                     });
328                     snapshotTasks(mTmpTasks);
329                 }
330             } finally {
331                 listener.onScreenOff();
332             }
333         });
334     }
335 
dump(PrintWriter pw, String prefix)336     void dump(PrintWriter pw, String prefix) {
337         mCache.dump(pw, prefix);
338     }
339 }
340