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(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             if (activity.isActivityTypeHome()) continue;
122             final Task task = activity.getTask();
123             if (task == null) continue;
124 
125             getClosingTasksInner(task, mTmpTasks);
126         }
127         snapshotTasks(mTmpTasks);
128         mTmpTasks.clear();
129         mSkipClosingAppSnapshotTasks.clear();
130     }
131 
132     /**
133      * Adds the given {@param tasks} to the list of tasks which should not have their snapshots
134      * taken upon the next processing of the set of closing apps. The caller is responsible for
135      * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot.
136      */
137     @VisibleForTesting
addSkipClosingAppSnapshotTasks(Set<Task> tasks)138     void addSkipClosingAppSnapshotTasks(Set<Task> tasks) {
139         if (shouldDisableSnapshots()) {
140             return;
141         }
142         for (Task task : tasks) {
143             mSkipClosingAppSnapshotTasks.add(task.mTaskId);
144         }
145     }
146 
snapshotTasks(ArraySet<Task> tasks)147     void snapshotTasks(ArraySet<Task> tasks) {
148         for (int i = tasks.size() - 1; i >= 0; i--) {
149             recordSnapshot(tasks.valueAt(i));
150         }
151     }
152 
153     /**
154      * The attributes of task snapshot are based on task configuration. But sometimes the
155      * configuration may have been changed during a transition, so supply the ChangeInfo that
156      * stored the previous appearance of the closing task.
157      */
recordSnapshot(Task task, Transition.ChangeInfo changeInfo)158     void recordSnapshot(Task task, Transition.ChangeInfo changeInfo) {
159         mCurrentChangeInfo = changeInfo;
160         try {
161             recordSnapshot(task);
162         } finally {
163             mCurrentChangeInfo = null;
164         }
165     }
166 
recordSnapshot(Task task)167     TaskSnapshot recordSnapshot(Task task) {
168         final TaskSnapshot snapshot = recordSnapshotInner(task);
169         if (snapshot != null && !task.isActivityTypeHome()) {
170             mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
171             task.onSnapshotChanged(snapshot);
172         }
173         return snapshot;
174     }
175 
176     /**
177      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW
178      * MANAGER LOCK WHEN CALLING THIS METHOD!
179      */
180     @Nullable
getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean isLowResolution)181     TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
182             boolean isLowResolution) {
183         return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
184                 && mPersistInfoProvider.enableLowResSnapshots());
185     }
186 
187     /**
188      * Returns the elapsed real time (in nanoseconds) at which a snapshot for the given task was
189      * last taken, or -1 if no such snapshot exists for that task.
190      */
getSnapshotCaptureTime(int taskId)191     long getSnapshotCaptureTime(int taskId) {
192         final TaskSnapshot snapshot = mCache.getSnapshot(taskId);
193         if (snapshot != null) {
194             return snapshot.getCaptureTime();
195         }
196         return -1;
197     }
198 
199     /**
200      * @see WindowManagerInternal#clearSnapshotCache
201      */
clearSnapshotCache()202     public void clearSnapshotCache() {
203         mCache.clearRunningCache();
204     }
205 
206     /**
207      * Find the window for a given task to take a snapshot. Top child of the task is usually the one
208      * we're looking for, but during app transitions, trampoline activities can appear in the
209      * children, which should be ignored.
210      */
findAppTokenForSnapshot(Task task)211     @Nullable protected ActivityRecord findAppTokenForSnapshot(Task task) {
212         return task.getActivity(ActivityRecord::canCaptureSnapshot);
213     }
214 
215 
216     @Override
use16BitFormat()217     protected boolean use16BitFormat() {
218         return mPersistInfoProvider.use16BitFormat();
219     }
220 
221     @Nullable
createImeSnapshot(@onNull Task task, int pixelFormat)222     private ScreenCapture.ScreenshotHardwareBuffer createImeSnapshot(@NonNull Task task,
223             int pixelFormat) {
224         if (task.getSurfaceControl() == null) {
225             if (DEBUG_SCREENSHOT) {
226                 Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
227             }
228             return null;
229         }
230         final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
231         ScreenCapture.ScreenshotHardwareBuffer imeBuffer = null;
232         if (imeWindow != null && imeWindow.isVisible()) {
233             final Rect bounds = imeWindow.getParentFrame();
234             bounds.offsetTo(0, 0);
235             imeBuffer = ScreenCapture.captureLayersExcluding(imeWindow.getSurfaceControl(),
236                     bounds, 1.0f, pixelFormat, null);
237         }
238         return imeBuffer;
239     }
240 
241     /**
242      * Create the snapshot of the IME surface on the task which used for placing on the closing
243      * task to keep IME visibility while app transitioning.
244      */
245     @Nullable
snapshotImeFromAttachedTask(@onNull Task task)246     ScreenCapture.ScreenshotHardwareBuffer snapshotImeFromAttachedTask(@NonNull Task task) {
247         // Check if the IME targets task ready to take the corresponding IME snapshot, if not,
248         // means the task is not yet visible for some reasons and no need to snapshot IME surface.
249         if (checkIfReadyToSnapshot(task) == null) {
250             return null;
251         }
252         final int pixelFormat = mPersistInfoProvider.use16BitFormat()
253                     ? PixelFormat.RGB_565
254                     : PixelFormat.RGBA_8888;
255         return createImeSnapshot(task, pixelFormat);
256     }
257 
258     @Override
getTopActivity(Task source)259     ActivityRecord getTopActivity(Task source) {
260         return source.getTopMostActivity();
261     }
262 
263     @Override
getTopFullscreenActivity(Task source)264     ActivityRecord getTopFullscreenActivity(Task source) {
265         return source.getTopFullscreenActivity();
266     }
267 
268     @Override
getTaskDescription(Task source)269     ActivityManager.TaskDescription getTaskDescription(Task source) {
270         return source.getTaskDescription();
271     }
272 
273     @Override
getLetterboxInsets(ActivityRecord topActivity)274     protected Rect getLetterboxInsets(ActivityRecord topActivity) {
275         return topActivity.getLetterboxInsets();
276     }
277 
getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks)278     void getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks) {
279         // Since RecentsAnimation will handle task snapshot while switching apps with the
280         // best capture timing (e.g. IME window capture),
281         // No need additional task capture while task is controlled by RecentsAnimation.
282         if (isAnimatingByRecents(task)) {
283             mSkipClosingAppSnapshotTasks.add(task.mTaskId);
284         }
285         // If the task of the app is not visible anymore, it means no other app in that task
286         // is opening. Thus, the task is closing.
287         if (!task.isVisible() && mSkipClosingAppSnapshotTasks.indexOf(task.mTaskId) < 0) {
288             outClosingTasks.add(task);
289         }
290     }
291 
removeAndDeleteSnapshot(int taskId, int userId)292     void removeAndDeleteSnapshot(int taskId, int userId) {
293         mCache.onIdRemoved(taskId);
294         mPersister.removeSnapshot(taskId, userId);
295     }
296 
removeSnapshotCache(int taskId)297     void removeSnapshotCache(int taskId) {
298         mCache.removeRunningEntry(taskId);
299     }
300 
301     /**
302      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
303      */
removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)304     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
305         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
306     }
307 
308     /**
309      * Called when screen is being turned off.
310      */
screenTurningOff(int displayId, ScreenOffListener listener)311     void screenTurningOff(int displayId, ScreenOffListener listener) {
312         if (shouldDisableSnapshots()) {
313             listener.onScreenOff();
314             return;
315         }
316 
317         // We can't take a snapshot when screen is off, so take a snapshot now!
318         mHandler.post(() -> {
319             try {
320                 synchronized (mService.mGlobalLock) {
321                     snapshotForSleeping(displayId);
322                 }
323             } finally {
324                 listener.onScreenOff();
325             }
326         });
327     }
328 
329     /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
snapshotForSleeping(int displayId)330     void snapshotForSleeping(int displayId) {
331         if (shouldDisableSnapshots() || !mService.mDisplayEnabled) {
332             return;
333         }
334         final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
335         if (displayContent == null) {
336             return;
337         }
338         // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
339         // secure lock to home.
340         final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
341                 && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
342         mTmpTasks.clear();
343         displayContent.forAllLeafTasks(task -> {
344             if (!allowSnapshotHome && task.isActivityTypeHome()) {
345                 return;
346             }
347             // Since RecentsAnimation will handle task snapshot while switching apps with the best
348             // capture timing (e.g. IME window capture), No need additional task capture while task
349             // is controlled by RecentsAnimation.
350             if (task.isVisible() && !isAnimatingByRecents(task)) {
351                 mTmpTasks.add(task);
352             }
353         }, true /* traverseTopToBottom */);
354         snapshotTasks(mTmpTasks);
355     }
356 }
357