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 com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.ActivityManager;
25 import android.graphics.PixelFormat;
26 import android.graphics.Rect;
27 import android.os.Environment;
28 import android.os.Handler;
29 import android.util.ArraySet;
30 import android.util.IntArray;
31 import android.util.Slog;
32 import android.view.Display;
33 import android.window.ScreenCapture;
34 import android.window.TaskSnapshot;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
38 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
39 
40 import java.util.Set;
41 
42 /**
43  * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
44  * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
45  * like without any copying.
46  * <p>
47  * System applications may retrieve a snapshot to represent the current state of a task, and draw
48  * them in their own process.
49  * <p>
50  * When we task becomes visible again, we show a starting window with the snapshot as the content to
51  * make app transitions more responsive.
52  * <p>
53  * To access this class, acquire the global window manager lock.
54  */
55 class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshotCache> {
56     static final String SNAPSHOTS_DIRNAME = "snapshots";
57 
58     private final TaskSnapshotPersister mPersister;
59     private final IntArray mSkipClosingAppSnapshotTasks = new IntArray();
60     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
61     private final Handler mHandler = new Handler();
62 
63     private final PersistInfoProvider mPersistInfoProvider;
64 
TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue)65     TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
66         super(service);
67         mPersistInfoProvider = createPersistInfoProvider(service,
68                 Environment::getDataSystemCeDirectory);
69         mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
70 
71         initialize(new TaskSnapshotCache(service, new AppSnapshotLoader(mPersistInfoProvider)));
72         final boolean snapshotEnabled =
73                 !service.mContext
74                         .getResources()
75                         .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
76         setSnapshotEnabled(snapshotEnabled);
77     }
78 
createPersistInfoProvider(WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver)79     static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
80             BaseAppSnapshotPersister.DirectoryResolver resolver) {
81         final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
82                 com.android.internal.R.dimen.config_highResTaskSnapshotScale);
83         final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
84                 com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
85 
86         if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
87             throw new RuntimeException("Low-res scale must be between 0 and 1");
88         }
89         if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
90             throw new RuntimeException("High-res scale must be between 0 and 1");
91         }
92         if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
93             throw new RuntimeException("High-res scale must be greater than low-res scale");
94         }
95 
96         final float lowResScaleFactor;
97         final boolean enableLowResSnapshots;
98         if (lowResTaskSnapshotScale > 0) {
99             lowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
100             enableLowResSnapshots = true;
101         } else {
102             lowResScaleFactor = 0;
103             enableLowResSnapshots = false;
104         }
105         final boolean use16BitFormat = service.mContext.getResources().getBoolean(
106                 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
107         return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME,
108                 enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
109     }
110 
111     // Still needed for legacy transition.(AppTransitionControllerTest)
handleClosingApps(ArraySet<ActivityRecord> closingApps)112     void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
113         if (shouldDisableSnapshots()) {
114             return;
115         }
116         // We need to take a snapshot of the task if and only if all activities of the task are
117         // either closing or hidden.
118         mTmpTasks.clear();
119         for (int i = closingApps.size() - 1; i >= 0; i--) {
120             final ActivityRecord activity = closingApps.valueAt(i);
121             final Task task = activity.getTask();
122             if (task == null) continue;
123 
124             getClosingTasksInner(task, mTmpTasks);
125         }
126         snapshotTasks(mTmpTasks);
127         mTmpTasks.clear();
128         mSkipClosingAppSnapshotTasks.clear();
129     }
130 
131     /**
132      * Adds the given {@param tasks} to the list of tasks which should not have their snapshots
133      * taken upon the next processing of the set of closing apps. The caller is responsible for
134      * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot.
135      */
136     @VisibleForTesting
addSkipClosingAppSnapshotTasks(Set<Task> tasks)137     void addSkipClosingAppSnapshotTasks(Set<Task> tasks) {
138         if (shouldDisableSnapshots()) {
139             return;
140         }
141         for (Task task : tasks) {
142             mSkipClosingAppSnapshotTasks.add(task.mTaskId);
143         }
144     }
145 
snapshotTasks(ArraySet<Task> tasks)146     void snapshotTasks(ArraySet<Task> tasks) {
147         snapshotTasks(tasks, false /* allowSnapshotHome */);
148     }
149 
recordSnapshot(Task task, boolean allowSnapshotHome)150     TaskSnapshot recordSnapshot(Task task, boolean allowSnapshotHome) {
151         final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
152         final TaskSnapshot snapshot = recordSnapshotInner(task, allowSnapshotHome);
153         if (!snapshotHome && snapshot != null) {
154             mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
155             task.onSnapshotChanged(snapshot);
156         }
157         return snapshot;
158     }
159 
snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome)160     private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
161         for (int i = tasks.size() - 1; i >= 0; i--) {
162             recordSnapshot(tasks.valueAt(i), allowSnapshotHome);
163         }
164     }
165 
166     /**
167      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW
168      * MANAGER LOCK WHEN CALLING THIS METHOD!
169      */
170     @Nullable
getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean isLowResolution)171     TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
172             boolean isLowResolution) {
173         return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
174                 && mPersistInfoProvider.enableLowResSnapshots());
175     }
176 
177     /**
178      * Returns the elapsed real time (in nanoseconds) at which a snapshot for the given task was
179      * last taken, or -1 if no such snapshot exists for that task.
180      */
getSnapshotCaptureTime(int taskId)181     long getSnapshotCaptureTime(int taskId) {
182         final TaskSnapshot snapshot = mCache.getSnapshot(taskId);
183         if (snapshot != null) {
184             return snapshot.getCaptureTime();
185         }
186         return -1;
187     }
188 
189     /**
190      * @see WindowManagerInternal#clearSnapshotCache
191      */
clearSnapshotCache()192     public void clearSnapshotCache() {
193         mCache.clearRunningCache();
194     }
195 
196     /**
197      * Find the window for a given task to take a snapshot. Top child of the task is usually the one
198      * we're looking for, but during app transitions, trampoline activities can appear in the
199      * children, which should be ignored.
200      */
findAppTokenForSnapshot(Task task)201     @Nullable protected ActivityRecord findAppTokenForSnapshot(Task task) {
202         return task.getActivity(ActivityRecord::canCaptureSnapshot);
203     }
204 
205 
206     @Override
use16BitFormat()207     protected boolean use16BitFormat() {
208         return mPersistInfoProvider.use16BitFormat();
209     }
210 
211     @Nullable
createImeSnapshot(@onNull Task task, int pixelFormat)212     private ScreenCapture.ScreenshotHardwareBuffer createImeSnapshot(@NonNull Task task,
213             int pixelFormat) {
214         if (task.getSurfaceControl() == null) {
215             if (DEBUG_SCREENSHOT) {
216                 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
217             }
218             return null;
219         }
220         final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
221         ScreenCapture.ScreenshotHardwareBuffer imeBuffer = null;
222         if (imeWindow != null && imeWindow.isVisible()) {
223             final Rect bounds = imeWindow.getParentFrame();
224             bounds.offsetTo(0, 0);
225             imeBuffer = ScreenCapture.captureLayersExcluding(imeWindow.getSurfaceControl(),
226                     bounds, 1.0f, pixelFormat, null);
227         }
228         return imeBuffer;
229     }
230 
231     /**
232      * Create the snapshot of the IME surface on the task which used for placing on the closing
233      * task to keep IME visibility while app transitioning.
234      */
235     @Nullable
snapshotImeFromAttachedTask(@onNull Task task)236     ScreenCapture.ScreenshotHardwareBuffer snapshotImeFromAttachedTask(@NonNull Task task) {
237         // Check if the IME targets task ready to take the corresponding IME snapshot, if not,
238         // means the task is not yet visible for some reasons and no need to snapshot IME surface.
239         if (checkIfReadyToSnapshot(task) == null) {
240             return null;
241         }
242         final int pixelFormat = mPersistInfoProvider.use16BitFormat()
243                     ? PixelFormat.RGB_565
244                     : PixelFormat.RGBA_8888;
245         return createImeSnapshot(task, pixelFormat);
246     }
247 
248     @Override
getTopActivity(Task source)249     ActivityRecord getTopActivity(Task source) {
250         return source.getTopMostActivity();
251     }
252 
253     @Override
getTopFullscreenActivity(Task source)254     ActivityRecord getTopFullscreenActivity(Task source) {
255         return source.getTopFullscreenActivity();
256     }
257 
258     @Override
getTaskDescription(Task source)259     ActivityManager.TaskDescription getTaskDescription(Task source) {
260         return source.getTaskDescription();
261     }
262 
getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks)263     void getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks) {
264         // Since RecentsAnimation will handle task snapshot while switching apps with the
265         // best capture timing (e.g. IME window capture),
266         // No need additional task capture while task is controlled by RecentsAnimation.
267         if (isAnimatingByRecents(task)) {
268             mSkipClosingAppSnapshotTasks.add(task.mTaskId);
269         }
270         // If the task of the app is not visible anymore, it means no other app in that task
271         // is opening. Thus, the task is closing.
272         if (!task.isVisible() && mSkipClosingAppSnapshotTasks.indexOf(task.mTaskId) < 0) {
273             outClosingTasks.add(task);
274         }
275     }
276 
notifyTaskRemovedFromRecents(int taskId, int userId)277     void notifyTaskRemovedFromRecents(int taskId, int userId) {
278         mCache.onIdRemoved(taskId);
279         mPersister.onTaskRemovedFromRecents(taskId, userId);
280     }
281 
removeSnapshotCache(int taskId)282     void removeSnapshotCache(int taskId) {
283         mCache.removeRunningEntry(taskId);
284     }
285 
286     /**
287      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
288      */
removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)289     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
290         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
291     }
292 
293     /**
294      * Called when screen is being turned off.
295      */
screenTurningOff(int displayId, ScreenOffListener listener)296     void screenTurningOff(int displayId, ScreenOffListener listener) {
297         if (shouldDisableSnapshots()) {
298             listener.onScreenOff();
299             return;
300         }
301 
302         // We can't take a snapshot when screen is off, so take a snapshot now!
303         mHandler.post(() -> {
304             try {
305                 synchronized (mService.mGlobalLock) {
306                     snapshotForSleeping(displayId);
307                 }
308             } finally {
309                 listener.onScreenOff();
310             }
311         });
312     }
313 
314     /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
snapshotForSleeping(int displayId)315     void snapshotForSleeping(int displayId) {
316         if (shouldDisableSnapshots() || !mService.mDisplayEnabled) {
317             return;
318         }
319         final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
320         if (displayContent == null) {
321             return;
322         }
323         mTmpTasks.clear();
324         displayContent.forAllTasks(task -> {
325             // Since RecentsAnimation will handle task snapshot while switching apps with the best
326             // capture timing (e.g. IME window capture), No need additional task capture while task
327             // is controlled by RecentsAnimation.
328             if (task.isVisible() && !isAnimatingByRecents(task)) {
329                 mTmpTasks.add(task);
330             }
331         });
332         // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
333         // secure lock to home.
334         final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
335                 && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
336         snapshotTasks(mTmpTasks, allowSnapshotHome);
337     }
338 }
339