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 android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS; 20 21 import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; 22 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE; 23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 25 26 import android.annotation.Nullable; 27 import android.app.ActivityManager; 28 import android.app.ActivityManager.StackId; 29 import android.app.ActivityManager.TaskSnapshot; 30 import android.content.pm.PackageManager; 31 import android.graphics.Bitmap; 32 import android.graphics.GraphicBuffer; 33 import android.graphics.Rect; 34 import android.os.Environment; 35 import android.os.Handler; 36 import android.util.ArraySet; 37 import android.util.Slog; 38 import android.view.DisplayListCanvas; 39 import android.view.RenderNode; 40 import android.view.ThreadedRenderer; 41 import android.view.WindowManager.LayoutParams; 42 import android.view.WindowManagerPolicy.ScreenOffListener; 43 import android.view.WindowManagerPolicy.StartingSurface; 44 45 import com.google.android.collect.Sets; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter; 49 50 import java.io.PrintWriter; 51 52 /** 53 * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and 54 * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we 55 * like without any copying. 56 * <p> 57 * System applications may retrieve a snapshot to represent the current state of a task, and draw 58 * them in their own process. 59 * <p> 60 * When we task becomes visible again, we show a starting window with the snapshot as the content to 61 * make app transitions more responsive. 62 * <p> 63 * To access this class, acquire the global window manager lock. 64 */ 65 class TaskSnapshotController { 66 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM; 67 68 /** 69 * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be 70 * used as the snapshot. 71 */ 72 @VisibleForTesting 73 static final int SNAPSHOT_MODE_REAL = 0; 74 75 /** 76 * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but 77 * we should try to use the app theme to create a dummy representation of the app. 78 */ 79 @VisibleForTesting 80 static final int SNAPSHOT_MODE_APP_THEME = 1; 81 82 /** 83 * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot. 84 */ 85 @VisibleForTesting 86 static final int SNAPSHOT_MODE_NONE = 2; 87 88 private final WindowManagerService mService; 89 90 private final TaskSnapshotCache mCache; 91 private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister( 92 Environment::getDataSystemCeDirectory); 93 private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister); 94 private final ArraySet<Task> mTmpTasks = new ArraySet<>(); 95 private final Handler mHandler = new Handler(); 96 97 /** 98 * Flag indicating whether we are running on an Android TV device. 99 */ 100 private final boolean mIsRunningOnTv; 101 102 /** 103 * Flag indicating whether we are running on an IoT device. 104 */ 105 private final boolean mIsRunningOnIoT; 106 107 /** 108 * Flag indicating whether we are running on an Android Wear device. 109 */ 110 private final boolean mIsRunningOnWear; 111 TaskSnapshotController(WindowManagerService service)112 TaskSnapshotController(WindowManagerService service) { 113 mService = service; 114 mCache = new TaskSnapshotCache(mService, mLoader); 115 mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature( 116 PackageManager.FEATURE_LEANBACK); 117 mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature( 118 PackageManager.FEATURE_EMBEDDED); 119 mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature( 120 PackageManager.FEATURE_WATCH); 121 } 122 systemReady()123 void systemReady() { 124 mPersister.start(); 125 } 126 onTransitionStarting()127 void onTransitionStarting() { 128 handleClosingApps(mService.mClosingApps); 129 } 130 131 /** 132 * Called when the visibility of an app changes outside of the regular app transition flow. 133 */ notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible)134 void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) { 135 if (!visible) { 136 handleClosingApps(Sets.newArraySet(appWindowToken)); 137 } 138 } 139 handleClosingApps(ArraySet<AppWindowToken> closingApps)140 private void handleClosingApps(ArraySet<AppWindowToken> closingApps) { 141 if (shouldDisableSnapshots()) { 142 return; 143 } 144 145 // We need to take a snapshot of the task if and only if all activities of the task are 146 // either closing or hidden. 147 getClosingTasks(closingApps, mTmpTasks); 148 snapshotTasks(mTmpTasks); 149 150 } 151 snapshotTasks(ArraySet<Task> tasks)152 private void snapshotTasks(ArraySet<Task> tasks) { 153 for (int i = tasks.size() - 1; i >= 0; i--) { 154 final Task task = tasks.valueAt(i); 155 final int mode = getSnapshotMode(task); 156 final TaskSnapshot snapshot; 157 switch (mode) { 158 case SNAPSHOT_MODE_NONE: 159 continue; 160 case SNAPSHOT_MODE_APP_THEME: 161 snapshot = drawAppThemeSnapshot(task); 162 break; 163 case SNAPSHOT_MODE_REAL: 164 snapshot = snapshotTask(task); 165 break; 166 default: 167 snapshot = null; 168 break; 169 } 170 if (snapshot != null) { 171 final GraphicBuffer buffer = snapshot.getSnapshot(); 172 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { 173 buffer.destroy(); 174 Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" 175 + buffer.getHeight()); 176 } else { 177 mCache.putSnapshot(task, snapshot); 178 mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); 179 if (task.getController() != null) { 180 task.getController().reportSnapshotChanged(snapshot); 181 } 182 } 183 } 184 } 185 } 186 187 /** 188 * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW 189 * MANAGER LOCK WHEN CALLING THIS METHOD! 190 */ getSnapshot(int taskId, int userId, boolean restoreFromDisk, boolean reducedResolution)191 @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk, 192 boolean reducedResolution) { 193 return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution 194 || DISABLE_FULL_SIZED_BITMAPS); 195 } 196 197 /** 198 * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW 199 * MANAGER LOCK WHEN CALLING THIS METHOD! 200 */ createStartingSurface(AppWindowToken token, TaskSnapshot snapshot)201 StartingSurface createStartingSurface(AppWindowToken token, 202 TaskSnapshot snapshot) { 203 return TaskSnapshotSurface.create(mService, token, snapshot); 204 } 205 snapshotTask(Task task)206 private TaskSnapshot snapshotTask(Task task) { 207 final AppWindowToken top = task.getTopChild(); 208 if (top == null) { 209 return null; 210 } 211 final WindowState mainWindow = top.findMainWindow(); 212 if (mainWindow == null) { 213 return null; 214 } 215 final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); 216 final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; 217 final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token, 218 -1, -1, false, scaleFraction, false, true); 219 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 220 return null; 221 } 222 return new TaskSnapshot(buffer, top.getConfiguration().orientation, 223 minRect(mainWindow.mContentInsets, mainWindow.mStableInsets), 224 isLowRamDevice /* reduced */, scaleFraction /* scale */); 225 } 226 shouldDisableSnapshots()227 private boolean shouldDisableSnapshots() { 228 return !ENABLE_TASK_SNAPSHOTS || mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT; 229 } 230 minRect(Rect rect1, Rect rect2)231 private Rect minRect(Rect rect1, Rect rect2) { 232 return new Rect(Math.min(rect1.left, rect2.left), 233 Math.min(rect1.top, rect2.top), 234 Math.min(rect1.right, rect2.right), 235 Math.min(rect1.bottom, rect2.bottom)); 236 } 237 238 /** 239 * Retrieves all closing tasks based on the list of closing apps during an app transition. 240 */ 241 @VisibleForTesting getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks)242 void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) { 243 outClosingTasks.clear(); 244 for (int i = closingApps.size() - 1; i >= 0; i--) { 245 final AppWindowToken atoken = closingApps.valueAt(i); 246 final Task task = atoken.getTask(); 247 248 // If the task of the app is not visible anymore, it means no other app in that task 249 // is opening. Thus, the task is closing. 250 if (task != null && !task.isVisible()) { 251 outClosingTasks.add(task); 252 } 253 } 254 } 255 256 @VisibleForTesting getSnapshotMode(Task task)257 int getSnapshotMode(Task task) { 258 final AppWindowToken topChild = task.getTopChild(); 259 if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) { 260 return SNAPSHOT_MODE_NONE; 261 } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) { 262 return SNAPSHOT_MODE_APP_THEME; 263 } else { 264 return SNAPSHOT_MODE_REAL; 265 } 266 } 267 268 /** 269 * If we are not allowed to take a real screenshot, this attempts to represent the app as best 270 * as possible by using the theme's window background. 271 */ drawAppThemeSnapshot(Task task)272 private TaskSnapshot drawAppThemeSnapshot(Task task) { 273 final AppWindowToken topChild = task.getTopChild(); 274 if (topChild == null) { 275 return null; 276 } 277 final WindowState mainWindow = topChild.findMainWindow(); 278 if (mainWindow == null) { 279 return null; 280 } 281 final int color = task.getTaskDescription().getBackgroundColor(); 282 final int statusBarColor = task.getTaskDescription().getStatusBarColor(); 283 final int navigationBarColor = task.getTaskDescription().getNavigationBarColor(); 284 final LayoutParams attrs = mainWindow.getAttrs(); 285 final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, 286 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor); 287 final int width = mainWindow.getFrameLw().width(); 288 final int height = mainWindow.getFrameLw().height(); 289 290 final RenderNode node = RenderNode.create("TaskSnapshotController", null); 291 node.setLeftTopRightBottom(0, 0, width, height); 292 node.setClipToBounds(false); 293 final DisplayListCanvas c = node.start(width, height); 294 c.drawColor(color); 295 decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets); 296 decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); 297 node.end(c); 298 final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); 299 if (hwBitmap == null) { 300 return null; 301 } 302 return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(), 303 topChild.getConfiguration().orientation, mainWindow.mStableInsets, 304 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */); 305 } 306 307 /** 308 * Called when an {@link AppWindowToken} has been removed. 309 */ onAppRemoved(AppWindowToken wtoken)310 void onAppRemoved(AppWindowToken wtoken) { 311 mCache.onAppRemoved(wtoken); 312 } 313 314 /** 315 * Called when the process of an {@link AppWindowToken} has died. 316 */ onAppDied(AppWindowToken wtoken)317 void onAppDied(AppWindowToken wtoken) { 318 mCache.onAppDied(wtoken); 319 } 320 notifyTaskRemovedFromRecents(int taskId, int userId)321 void notifyTaskRemovedFromRecents(int taskId, int userId) { 322 mCache.onTaskRemoved(taskId); 323 mPersister.onTaskRemovedFromRecents(taskId, userId); 324 } 325 326 /** 327 * See {@link TaskSnapshotPersister#removeObsoleteFiles} 328 */ removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)329 void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) { 330 mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds); 331 } 332 333 /** 334 * Temporarily pauses/unpauses persisting of task snapshots. 335 * 336 * @param paused Whether task snapshot persisting should be paused. 337 */ setPersisterPaused(boolean paused)338 void setPersisterPaused(boolean paused) { 339 mPersister.setPaused(paused); 340 } 341 342 /** 343 * Called when screen is being turned off. 344 */ screenTurningOff(ScreenOffListener listener)345 void screenTurningOff(ScreenOffListener listener) { 346 if (shouldDisableSnapshots()) { 347 listener.onScreenOff(); 348 return; 349 } 350 351 // We can't take a snapshot when screen is off, so take a snapshot now! 352 mHandler.post(() -> { 353 try { 354 synchronized (mService.mWindowMap) { 355 mTmpTasks.clear(); 356 mService.mRoot.forAllTasks(task -> { 357 if (task.isVisible()) { 358 mTmpTasks.add(task); 359 } 360 }); 361 snapshotTasks(mTmpTasks); 362 } 363 } finally { 364 listener.onScreenOff(); 365 } 366 }); 367 } 368 dump(PrintWriter pw, String prefix)369 void dump(PrintWriter pw, String prefix) { 370 mCache.dump(pw, prefix); 371 } 372 } 373