• 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 static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
22 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25 
26 import android.annotation.Nullable;
27 import android.app.ActivityManager;
28 import android.app.ActivityManager.StackId;
29 import android.app.ActivityManager.TaskSnapshot;
30 import android.content.pm.PackageManager;
31 import android.graphics.Bitmap;
32 import android.graphics.GraphicBuffer;
33 import android.graphics.Rect;
34 import android.os.Environment;
35 import android.os.Handler;
36 import android.util.ArraySet;
37 import android.util.Slog;
38 import android.view.DisplayListCanvas;
39 import android.view.RenderNode;
40 import android.view.ThreadedRenderer;
41 import android.view.WindowManager.LayoutParams;
42 import android.view.WindowManagerPolicy.ScreenOffListener;
43 import android.view.WindowManagerPolicy.StartingSurface;
44 
45 import com.google.android.collect.Sets;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
49 
50 import java.io.PrintWriter;
51 
52 /**
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  */
65 class TaskSnapshotController {
66     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
67 
68     /**
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 
88     private final WindowManagerService mService;
89 
90     private final TaskSnapshotCache mCache;
91     private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
92             Environment::getDataSystemCeDirectory);
93     private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
94     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
95     private final Handler mHandler = new Handler();
96 
97     /**
98      * Flag indicating whether we are running on an Android TV device.
99      */
100     private final boolean mIsRunningOnTv;
101 
102     /**
103      * Flag indicating whether we are running on an IoT device.
104      */
105     private final boolean mIsRunningOnIoT;
106 
107     /**
108      * Flag indicating whether we are running on an Android Wear device.
109      */
110     private final boolean mIsRunningOnWear;
111 
TaskSnapshotController(WindowManagerService service)112     TaskSnapshotController(WindowManagerService service) {
113         mService = service;
114         mCache = new TaskSnapshotCache(mService, mLoader);
115         mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
116                 PackageManager.FEATURE_LEANBACK);
117         mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
118                 PackageManager.FEATURE_EMBEDDED);
119         mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
120             PackageManager.FEATURE_WATCH);
121     }
122 
systemReady()123     void systemReady() {
124         mPersister.start();
125     }
126 
onTransitionStarting()127     void onTransitionStarting() {
128         handleClosingApps(mService.mClosingApps);
129     }
130 
131     /**
132      * Called when the visibility of an app changes outside of the regular app transition flow.
133      */
notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible)134     void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
135         if (!visible) {
136             handleClosingApps(Sets.newArraySet(appWindowToken));
137         }
138     }
139 
handleClosingApps(ArraySet<AppWindowToken> closingApps)140     private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
141         if (shouldDisableSnapshots()) {
142             return;
143         }
144 
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.
147         getClosingTasks(closingApps, mTmpTasks);
148         snapshotTasks(mTmpTasks);
149 
150     }
151 
snapshotTasks(ArraySet<Task> tasks)152     private void snapshotTasks(ArraySet<Task> tasks) {
153         for (int i = tasks.size() - 1; i >= 0; i--) {
154             final Task task = tasks.valueAt(i);
155             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;
169             }
170             if (snapshot != null) {
171                 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                     }
182                 }
183             }
184         }
185     }
186 
187     /**
188      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
189      * MANAGER LOCK WHEN CALLING THIS METHOD!
190      */
getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean reducedResolution)191     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
192             boolean reducedResolution) {
193         return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
194                 || DISABLE_FULL_SIZED_BITMAPS);
195     }
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      */
createStartingSurface(AppWindowToken token, TaskSnapshot snapshot)201     StartingSurface createStartingSurface(AppWindowToken token,
202             TaskSnapshot snapshot) {
203         return TaskSnapshotSurface.create(mService, token, snapshot);
204     }
205 
snapshotTask(Task task)206     private TaskSnapshot snapshotTask(Task task) {
207         final AppWindowToken top = task.getTopChild();
208         if (top == null) {
209             return null;
210         }
211         final WindowState mainWindow = top.findMainWindow();
212         if (mainWindow == null) {
213             return null;
214         }
215         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
216         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
217         final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
218                 -1, -1, false, scaleFraction, false, true);
219         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
220             return null;
221         }
222         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
223                 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets),
224                 isLowRamDevice /* reduced */, scaleFraction /* scale */);
225     }
226 
shouldDisableSnapshots()227     private boolean shouldDisableSnapshots() {
228         return !ENABLE_TASK_SNAPSHOTS || mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
229     }
230 
minRect(Rect rect1, Rect rect2)231     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));
236     }
237 
238     /**
239      * Retrieves all closing tasks based on the list of closing apps during an app transition.
240      */
241     @VisibleForTesting
getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks)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);
246             final Task task = atoken.getTask();
247 
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.
250             if (task != null && !task.isVisible()) {
251                 outClosingTasks.add(task);
252             }
253         }
254     }
255 
256     @VisibleForTesting
getSnapshotMode(Task task)257     int getSnapshotMode(Task task) {
258         final AppWindowToken topChild = task.getTopChild();
259         if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
260             return SNAPSHOT_MODE_NONE;
261         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
262             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      */
drawAppThemeSnapshot(Task task)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();
282         final int statusBarColor = task.getTaskDescription().getStatusBarColor();
283         final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
284         final LayoutParams attrs = mainWindow.getAttrs();
285         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
286                 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
287         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);
295         decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
296         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
297         node.end(c);
298         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
299         if (hwBitmap == null) {
300             return null;
301         }
302         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
303                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
304                 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
305     }
306 
307     /**
308      * Called when an {@link AppWindowToken} has been removed.
309      */
onAppRemoved(AppWindowToken wtoken)310     void onAppRemoved(AppWindowToken wtoken) {
311         mCache.onAppRemoved(wtoken);
312     }
313 
314     /**
315      * Called when the process of an {@link AppWindowToken} has died.
316      */
onAppDied(AppWindowToken wtoken)317     void onAppDied(AppWindowToken wtoken) {
318         mCache.onAppDied(wtoken);
319     }
320 
notifyTaskRemovedFromRecents(int taskId, int userId)321     void notifyTaskRemovedFromRecents(int taskId, int userId) {
322         mCache.onTaskRemoved(taskId);
323         mPersister.onTaskRemovedFromRecents(taskId, userId);
324     }
325 
326     /**
327      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
328      */
removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)329     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
330         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
331     }
332 
333     /**
334      * Temporarily pauses/unpauses persisting of task snapshots.
335      *
336      * @param paused Whether task snapshot persisting should be paused.
337      */
setPersisterPaused(boolean paused)338     void setPersisterPaused(boolean paused) {
339         mPersister.setPaused(paused);
340     }
341 
342     /**
343      * Called when screen is being turned off.
344      */
screenTurningOff(ScreenOffListener listener)345     void screenTurningOff(ScreenOffListener listener) {
346         if (shouldDisableSnapshots()) {
347             listener.onScreenOff();
348             return;
349         }
350 
351         // We can't take a snapshot when screen is off, so take a snapshot now!
352         mHandler.post(() -> {
353             try {
354                 synchronized (mService.mWindowMap) {
355                     mTmpTasks.clear();
356                     mService.mRoot.forAllTasks(task -> {
357                         if (task.isVisible()) {
358                             mTmpTasks.add(task);
359                         }
360                     });
361                     snapshotTasks(mTmpTasks);
362                 }
363             } finally {
364                 listener.onScreenOff();
365             }
366         });
367     }
368 
dump(PrintWriter pw, String prefix)369     void dump(PrintWriter pw, String prefix) {
370         mCache.dump(pw, prefix);
371     }
372 }
373