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