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