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