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.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; 20 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE; 21 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; 22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 24 25 import android.annotation.Nullable; 26 import android.app.ActivityManager; 27 import android.app.ActivityManager.TaskSnapshot; 28 import android.content.pm.PackageManager; 29 import android.graphics.Bitmap; 30 import android.graphics.GraphicBuffer; 31 import android.graphics.PixelFormat; 32 import android.graphics.Rect; 33 import android.os.Environment; 34 import android.os.Handler; 35 import android.util.ArraySet; 36 import android.util.Slog; 37 import android.view.DisplayListCanvas; 38 import android.view.RenderNode; 39 import android.view.SurfaceControl; 40 import android.view.ThreadedRenderer; 41 import android.view.View; 42 import android.view.WindowManager.LayoutParams; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.graphics.ColorUtils; 46 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; 47 import com.android.server.policy.WindowManagerPolicy.StartingSurface; 48 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter; 49 import com.android.server.wm.utils.InsetUtils; 50 51 import com.google.android.collect.Sets; 52 53 import java.io.PrintWriter; 54 55 /** 56 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and 57 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we 58 * like without any copying. 59 * <p> 60 * System applications may retrieve a snapshot to represent the current state of a task, and draw 61 * them in their own process. 62 * <p> 63 * When we task becomes visible again, we show a starting window with the snapshot as the content to 64 * make app transitions more responsive. 65 * <p> 66 * To access this class, acquire the global window manager lock. 67 */ 68 class TaskSnapshotController { 69 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM; 70 71 /** 72 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be 73 * used as the snapshot. 74 */ 75 @VisibleForTesting 76 static final int SNAPSHOT_MODE_REAL = 0; 77 78 /** 79 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but 80 * we should try to use the app theme to create a dummy representation of the app. 81 */ 82 @VisibleForTesting 83 static final int SNAPSHOT_MODE_APP_THEME = 1; 84 85 /** 86 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot. 87 */ 88 @VisibleForTesting 89 static final int SNAPSHOT_MODE_NONE = 2; 90 91 private final WindowManagerService mService; 92 93 private final TaskSnapshotCache mCache; 94 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister( 95 Environment::getDataSystemCeDirectory); 96 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister); 97 private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>(); 98 private final ArraySet<Task> mTmpTasks = new ArraySet<>(); 99 private final Handler mHandler = new Handler(); 100 101 private final Rect mTmpRect = new Rect(); 102 103 /** 104 * Flag indicating whether we are running on an Android TV device. 105 */ 106 private final boolean mIsRunningOnTv; 107 108 /** 109 * Flag indicating whether we are running on an IoT device. 110 */ 111 private final boolean mIsRunningOnIoT; 112 113 /** 114 * Flag indicating whether we are running on an Android Wear device. 115 */ 116 private final boolean mIsRunningOnWear; 117 TaskSnapshotController(WindowManagerService service)118 TaskSnapshotController(WindowManagerService service) { 119 mService = service; 120 mCache = new TaskSnapshotCache(mService, mLoader); 121 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature( 122 PackageManager.FEATURE_LEANBACK); 123 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature( 124 PackageManager.FEATURE_EMBEDDED); 125 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature( 126 PackageManager.FEATURE_WATCH); 127 } 128 systemReady()129 void systemReady() { 130 mPersister.start(); 131 } 132 onTransitionStarting()133 void onTransitionStarting() { 134 handleClosingApps(mService.mClosingApps); 135 } 136 137 /** 138 * Called when the visibility of an app changes outside of the regular app transition flow. 139 */ notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible)140 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) { 141 if (!visible) { 142 handleClosingApps(Sets.newArraySet(appWindowToken)); 143 } 144 } 145 handleClosingApps(ArraySet<AppWindowToken> closingApps)146 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) { 147 if (shouldDisableSnapshots()) { 148 return; 149 } 150 151 // We need to take a snapshot of the task if and only if all activities of the task are 152 // either closing or hidden. 153 getClosingTasks(closingApps, mTmpTasks); 154 snapshotTasks(mTmpTasks); 155 mSkipClosingAppSnapshotTasks.clear(); 156 } 157 158 /** 159 * Adds the given {@param tasks} to the list of tasks which should not have their snapshots 160 * taken upon the next processing of the set of closing apps. The caller is responsible for 161 * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot. 162 */ 163 @VisibleForTesting addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks)164 void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) { 165 mSkipClosingAppSnapshotTasks.addAll(tasks); 166 } 167 snapshotTasks(ArraySet<Task> tasks)168 void snapshotTasks(ArraySet<Task> tasks) { 169 for (int i = tasks.size() - 1; i >= 0; i--) { 170 final Task task = tasks.valueAt(i); 171 final int mode = getSnapshotMode(task); 172 final TaskSnapshot snapshot; 173 switch (mode) { 174 case SNAPSHOT_MODE_NONE: 175 continue; 176 case SNAPSHOT_MODE_APP_THEME: 177 snapshot = drawAppThemeSnapshot(task); 178 break; 179 case SNAPSHOT_MODE_REAL: 180 snapshot = snapshotTask(task); 181 break; 182 default: 183 snapshot = null; 184 break; 185 } 186 if (snapshot != null) { 187 final GraphicBuffer buffer = snapshot.getSnapshot(); 188 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { 189 buffer.destroy(); 190 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" 191 + buffer.getHeight()); 192 } else { 193 mCache.putSnapshot(task, snapshot); 194 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); 195 if (task.getController() != null) { 196 task.getController().reportSnapshotChanged(snapshot); 197 } 198 } 199 } 200 } 201 } 202 203 /** 204 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW 205 * MANAGER LOCK WHEN CALLING THIS METHOD! 206 */ getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean reducedResolution)207 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk, 208 boolean reducedResolution) { 209 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution 210 || DISABLE_FULL_SIZED_BITMAPS); 211 } 212 213 /** 214 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW 215 * MANAGER LOCK WHEN CALLING THIS METHOD! 216 */ createStartingSurface(AppWindowToken token, TaskSnapshot snapshot)217 StartingSurface createStartingSurface(AppWindowToken token, 218 TaskSnapshot snapshot) { 219 return TaskSnapshotSurface.create(mService, token, snapshot); 220 } 221 snapshotTask(Task task)222 private TaskSnapshot snapshotTask(Task task) { 223 final AppWindowToken top = task.getTopChild(); 224 if (top == null) { 225 return null; 226 } 227 final WindowState mainWindow = top.findMainWindow(); 228 if (mainWindow == null) { 229 return null; 230 } 231 if (!mService.mPolicy.isScreenOn()) { 232 if (DEBUG_SCREENSHOT) { 233 Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); 234 } 235 return null; 236 } 237 if (task.getSurfaceControl() == null) { 238 return null; 239 } 240 241 if (top.hasCommittedReparentToAnimationLeash()) { 242 if (DEBUG_SCREENSHOT) { 243 Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + top); 244 } 245 return null; 246 } 247 248 final boolean hasVisibleChild = top.forAllWindows( 249 // Ensure at least one window for the top app is visible before attempting to take 250 // a screenshot. Visible here means that the WSA surface is shown and has an alpha 251 // greater than 0. 252 ws -> (ws.mAppToken == null || ws.mAppToken.isSurfaceShowing()) 253 && ws.mWinAnimator != null && ws.mWinAnimator.getShown() 254 && ws.mWinAnimator.mLastAlpha > 0f, true); 255 256 if (!hasVisibleChild) { 257 if (DEBUG_SCREENSHOT) { 258 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task); 259 } 260 return null; 261 } 262 263 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); 264 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; 265 task.getBounds(mTmpRect); 266 mTmpRect.offsetTo(0, 0); 267 268 final GraphicBuffer buffer = SurfaceControl.captureLayers( 269 task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); 270 final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; 271 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 272 if (DEBUG_SCREENSHOT) { 273 Slog.w(TAG_WM, "Failed to take screenshot for " + task); 274 } 275 return null; 276 } 277 return new TaskSnapshot(buffer, top.getConfiguration().orientation, 278 getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */, 279 true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), 280 !top.fillsParent() || isWindowTranslucent); 281 } 282 shouldDisableSnapshots()283 private boolean shouldDisableSnapshots() { 284 return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT; 285 } 286 getInsets(WindowState state)287 private Rect getInsets(WindowState state) { 288 // XXX(b/72757033): These are insets relative to the window frame, but we're really 289 // interested in the insets relative to the task bounds. 290 final Rect insets = minRect(state.mContentInsets, state.mStableInsets); 291 InsetUtils.addInsets(insets, state.mAppToken.getLetterboxInsets()); 292 return insets; 293 } 294 minRect(Rect rect1, Rect rect2)295 private Rect minRect(Rect rect1, Rect rect2) { 296 return new Rect(Math.min(rect1.left, rect2.left), 297 Math.min(rect1.top, rect2.top), 298 Math.min(rect1.right, rect2.right), 299 Math.min(rect1.bottom, rect2.bottom)); 300 } 301 302 /** 303 * Retrieves all closing tasks based on the list of closing apps during an app transition. 304 */ 305 @VisibleForTesting getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks)306 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) { 307 outClosingTasks.clear(); 308 for (int i = closingApps.size() - 1; i >= 0; i--) { 309 final AppWindowToken atoken = closingApps.valueAt(i); 310 final Task task = atoken.getTask(); 311 312 // If the task of the app is not visible anymore, it means no other app in that task 313 // is opening. Thus, the task is closing. 314 if (task != null && !task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) { 315 outClosingTasks.add(task); 316 } 317 } 318 } 319 320 @VisibleForTesting getSnapshotMode(Task task)321 int getSnapshotMode(Task task) { 322 final AppWindowToken topChild = task.getTopChild(); 323 if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) { 324 return SNAPSHOT_MODE_NONE; 325 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) { 326 return SNAPSHOT_MODE_APP_THEME; 327 } else { 328 return SNAPSHOT_MODE_REAL; 329 } 330 } 331 332 /** 333 * If we are not allowed to take a real screenshot, this attempts to represent the app as best 334 * as possible by using the theme's window background. 335 */ drawAppThemeSnapshot(Task task)336 private TaskSnapshot drawAppThemeSnapshot(Task task) { 337 final AppWindowToken topChild = task.getTopChild(); 338 if (topChild == null) { 339 return null; 340 } 341 final WindowState mainWindow = topChild.findMainWindow(); 342 if (mainWindow == null) { 343 return null; 344 } 345 final int color = ColorUtils.setAlphaComponent( 346 task.getTaskDescription().getBackgroundColor(), 255); 347 final int statusBarColor = task.getTaskDescription().getStatusBarColor(); 348 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor(); 349 final LayoutParams attrs = mainWindow.getAttrs(); 350 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, 351 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor); 352 final int width = mainWindow.getFrameLw().width(); 353 final int height = mainWindow.getFrameLw().height(); 354 355 final RenderNode node = RenderNode.create("TaskSnapshotController", null); 356 node.setLeftTopRightBottom(0, 0, width, height); 357 node.setClipToBounds(false); 358 final DisplayListCanvas c = node.start(width, height); 359 c.drawColor(color); 360 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets); 361 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); 362 node.end(c); 363 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); 364 if (hwBitmap == null) { 365 return null; 366 } 367 // Note, the app theme snapshot is never translucent because we enforce a non-translucent 368 // color above 369 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(), 370 topChild.getConfiguration().orientation, mainWindow.mStableInsets, 371 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */, 372 false /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), 373 false); 374 } 375 376 /** 377 * Called when an {@link AppWindowToken} has been removed. 378 */ onAppRemoved(AppWindowToken wtoken)379 void onAppRemoved(AppWindowToken wtoken) { 380 mCache.onAppRemoved(wtoken); 381 } 382 383 /** 384 * Called when the process of an {@link AppWindowToken} has died. 385 */ onAppDied(AppWindowToken wtoken)386 void onAppDied(AppWindowToken wtoken) { 387 mCache.onAppDied(wtoken); 388 } 389 notifyTaskRemovedFromRecents(int taskId, int userId)390 void notifyTaskRemovedFromRecents(int taskId, int userId) { 391 mCache.onTaskRemoved(taskId); 392 mPersister.onTaskRemovedFromRecents(taskId, userId); 393 } 394 395 /** 396 * See {@link TaskSnapshotPersister#removeObsoleteFiles} 397 */ removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)398 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) { 399 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds); 400 } 401 402 /** 403 * Temporarily pauses/unpauses persisting of task snapshots. 404 * 405 * @param paused Whether task snapshot persisting should be paused. 406 */ setPersisterPaused(boolean paused)407 void setPersisterPaused(boolean paused) { 408 mPersister.setPaused(paused); 409 } 410 411 /** 412 * Called when screen is being turned off. 413 */ screenTurningOff(ScreenOffListener listener)414 void screenTurningOff(ScreenOffListener listener) { 415 if (shouldDisableSnapshots()) { 416 listener.onScreenOff(); 417 return; 418 } 419 420 // We can't take a snapshot when screen is off, so take a snapshot now! 421 mHandler.post(() -> { 422 try { 423 synchronized (mService.mWindowMap) { 424 mTmpTasks.clear(); 425 mService.mRoot.forAllTasks(task -> { 426 if (task.isVisible()) { 427 mTmpTasks.add(task); 428 } 429 }); 430 snapshotTasks(mTmpTasks); 431 } 432 } finally { 433 listener.onScreenOff(); 434 } 435 }); 436 } 437 438 /** 439 * @return The SystemUI visibility flags for the top fullscreen window in the given 440 * {@param task}. 441 */ getSystemUiVisibility(Task task)442 private int getSystemUiVisibility(Task task) { 443 final AppWindowToken topFullscreenToken = task.getTopFullscreenAppToken(); 444 final WindowState topFullscreenWindow = topFullscreenToken != null 445 ? topFullscreenToken.getTopFullscreenWindow() 446 : null; 447 if (topFullscreenWindow != null) { 448 return topFullscreenWindow.getSystemUiVisibility(); 449 } 450 return 0; 451 } 452 dump(PrintWriter pw, String prefix)453 void dump(PrintWriter pw, String prefix) { 454 mCache.dump(pw, prefix); 455 } 456 } 457