1 /* 2 * Copyright (C) 2020 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.wm.shell.startingsurface; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE; 22 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL; 23 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION; 24 25 import android.annotation.CallSuper; 26 import android.annotation.NonNull; 27 import android.app.TaskInfo; 28 import android.app.WindowConfiguration; 29 import android.content.Context; 30 import android.content.res.Configuration; 31 import android.graphics.Color; 32 import android.hardware.display.DisplayManager; 33 import android.util.SparseArray; 34 import android.view.IWindow; 35 import android.view.SurfaceControl; 36 import android.view.WindowManager; 37 import android.view.WindowlessWindowManager; 38 import android.window.SplashScreenView; 39 import android.window.StartingWindowInfo; 40 import android.window.StartingWindowInfo.StartingWindowType; 41 import android.window.StartingWindowRemovalInfo; 42 import android.window.TaskSnapshot; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.protolog.ProtoLog; 46 import com.android.launcher3.icons.IconProvider; 47 import com.android.wm.shell.common.ShellExecutor; 48 import com.android.wm.shell.protolog.ShellProtoLogGroup; 49 import com.android.wm.shell.shared.TransactionPool; 50 import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; 51 52 /** 53 * A class which able to draw splash screen or snapshot as the starting window for a task. 54 */ 55 @ShellSplashscreenThread 56 public class StartingSurfaceDrawer { 57 58 private final ShellExecutor mSplashScreenExecutor; 59 @VisibleForTesting 60 final SplashscreenContentDrawer mSplashscreenContentDrawer; 61 @VisibleForTesting 62 final SplashscreenWindowCreator mSplashscreenWindowCreator; 63 private final SnapshotWindowCreator mSnapshotWindowCreator; 64 private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator; 65 private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator; 66 67 @VisibleForTesting 68 final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager(); 69 // Windowless surface could co-exist with starting window in a task. 70 @VisibleForTesting 71 final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager(); 72 /** 73 * @param splashScreenExecutor The thread used to control add and remove starting window. 74 */ StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, IconProvider iconProvider, TransactionPool pool)75 public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, 76 IconProvider iconProvider, TransactionPool pool) { 77 mSplashScreenExecutor = splashScreenExecutor; 78 final DisplayManager displayManager = context.getSystemService(DisplayManager.class); 79 mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool); 80 displayManager.getDisplay(DEFAULT_DISPLAY); 81 82 mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer, 83 context, splashScreenExecutor, displayManager, mWindowRecords); 84 mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor, 85 mWindowRecords); 86 mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator( 87 mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager, 88 mWindowlessRecords, pool); 89 mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator( 90 mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool); 91 } 92 setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy)93 void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) { 94 mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy); 95 mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy); 96 } 97 98 /** 99 * Called when a task need a splash screen starting window. 100 * 101 * @param suggestType The suggestion type to draw the splash screen. 102 */ addSplashScreenStartingWindow(StartingWindowInfo windowInfo, @StartingWindowType int suggestType)103 void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, 104 @StartingWindowType int suggestType) { 105 mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType); 106 } 107 getStartingWindowBackgroundColorForTask(int taskId)108 int getStartingWindowBackgroundColorForTask(int taskId) { 109 final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId); 110 if (startingWindowRecord == null) { 111 return Color.TRANSPARENT; 112 } 113 return startingWindowRecord.getBGColor(); 114 } 115 estimateTaskBackgroundColor(TaskInfo taskInfo)116 int estimateTaskBackgroundColor(TaskInfo taskInfo) { 117 return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo); 118 } 119 120 /** 121 * Called when a task need a snapshot starting window. 122 */ makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot)123 void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) { 124 mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot); 125 } 126 127 /** 128 * Called when the content of a task is ready to show, starting window can be removed. 129 */ removeStartingWindow(StartingWindowRemovalInfo removalInfo)130 public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { 131 if (removalInfo.windowlessSurface) { 132 mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately); 133 } else { 134 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 135 "Task start finish, remove starting surface for task: %d", 136 removalInfo.taskId); 137 mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately); 138 } 139 } 140 141 /** 142 * Create a windowless starting surface and attach to the root surface. 143 */ addWindowlessStartingSurface(StartingWindowInfo windowInfo)144 void addWindowlessStartingSurface(StartingWindowInfo windowInfo) { 145 if (windowInfo.taskSnapshot != null) { 146 mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo, 147 windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor); 148 } else { 149 mWindowlessSplashWindowCreator.addSplashScreenStartingWindow( 150 windowInfo, windowInfo.rootSurface); 151 } 152 } 153 154 /** 155 * Clear all starting windows immediately. 156 */ clearAllWindows()157 public void clearAllWindows() { 158 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 159 "Clear all starting windows immediately"); 160 mWindowRecords.clearAllWindows(); 161 mWindowlessRecords.clearAllWindows(); 162 } 163 164 /** 165 * Called when the Task wants to copy the splash screen. 166 */ copySplashScreenView(int taskId)167 public void copySplashScreenView(int taskId) { 168 mSplashscreenWindowCreator.copySplashScreenView(taskId); 169 } 170 171 /** 172 * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy 173 * or when the Activity is clean up. 174 * 175 * @param taskId The Task id on which the splash screen was attached 176 */ onAppSplashScreenViewRemoved(int taskId)177 public void onAppSplashScreenViewRemoved(int taskId) { 178 mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId); 179 } 180 onImeDrawnOnTask(int taskId)181 void onImeDrawnOnTask(int taskId) { 182 onImeDrawnOnTask(mWindowRecords, taskId); 183 onImeDrawnOnTask(mWindowlessRecords, taskId); 184 } 185 onImeDrawnOnTask(StartingWindowRecordManager records, int taskId)186 private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) { 187 final StartingSurfaceDrawer.StartingWindowRecord sRecord = 188 records.getRecord(taskId); 189 final SnapshotRecord record = sRecord instanceof SnapshotRecord 190 ? (SnapshotRecord) sRecord : null; 191 if (record != null && record.hasImeSurface()) { 192 records.removeWindow(taskId); 193 } 194 } 195 196 static class WindowlessStartingWindow extends WindowlessWindowManager { 197 SurfaceControl mChildSurface; 198 WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface)199 WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) { 200 super(c, rootSurface, null /* hostInputToken */); 201 } 202 203 @Override getParentSurface(IWindow window, WindowManager.LayoutParams attrs)204 protected SurfaceControl getParentSurface(IWindow window, 205 WindowManager.LayoutParams attrs) { 206 final SurfaceControl.Builder builder = new SurfaceControl.Builder() 207 .setContainerLayer() 208 .setName("Windowless window") 209 .setHidden(false) 210 .setParent(mRootSurface) 211 .setCallsite("WindowlessStartingWindow#attachToParentSurface"); 212 mChildSurface = builder.build(); 213 try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) { 214 t.setLayer(mChildSurface, Integer.MAX_VALUE); 215 t.apply(); 216 } 217 return mChildSurface; 218 } 219 } 220 abstract static class StartingWindowRecord { 221 protected int mBGColor; 222 223 /** 224 * Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible. 225 * @param info The removal info sent from the task organizer controller in the WM core. 226 * @param immediately {@code true} means removing the starting window immediately, 227 * {@code false} otherwise. 228 * @return {@code true} means {@link StartingWindowRecordManager} can safely remove the 229 * record itself. {@code false} means {@link StartingWindowRecordManager} requires 230 * to manage the record reference and remove it later. 231 */ removeIfPossible(StartingWindowRemovalInfo info, boolean immediately)232 abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately); getBGColor()233 int getBGColor() { 234 return mBGColor; 235 } 236 } 237 238 abstract static class SnapshotRecord extends StartingWindowRecord { 239 private static final long DELAY_REMOVAL_TIME_GENERAL = 100; 240 /** 241 * The max delay time in milliseconds for removing the task snapshot window with IME 242 * visible. 243 * Ideally the delay time will be shorter when receiving 244 * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. 245 */ 246 private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; 247 248 /** 249 * The max delay time in milliseconds for removing the task snapshot window with IME 250 * visible after the fixed rotation finished. 251 * Ideally the delay time will be shorter when receiving 252 * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. 253 */ 254 private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000; 255 256 private final Runnable mScheduledRunnable = this::removeImmediately; 257 258 @WindowConfiguration.ActivityType protected final int mActivityType; 259 protected final ShellExecutor mRemoveExecutor; 260 private final int mTaskId; 261 private final StartingWindowRecordManager mRecordManager; 262 SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId, StartingWindowRecordManager recordManager)263 SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId, 264 StartingWindowRecordManager recordManager) { 265 mActivityType = activityType; 266 mRemoveExecutor = removeExecutor; 267 mTaskId = taskId; 268 mRecordManager = recordManager; 269 } 270 271 @Override removeIfPossible(StartingWindowRemovalInfo info, boolean immediately)272 public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { 273 if (immediately 274 // Show the latest content as soon as possible for unlocking to home. 275 || mActivityType == ACTIVITY_TYPE_HOME 276 || info.deferRemoveMode == DEFER_MODE_NONE) { 277 removeImmediately(); 278 return true; 279 } 280 scheduleRemove(info.deferRemoveMode); 281 return false; 282 } 283 scheduleRemove(@tartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode)284 void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) { 285 mRemoveExecutor.removeCallbacks(mScheduledRunnable); 286 final long delayRemovalTime; 287 switch (deferRemoveForImeMode) { 288 case DEFER_MODE_ROTATION: 289 delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION; 290 break; 291 case DEFER_MODE_NORMAL: 292 delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE; 293 break; 294 default: 295 delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL; 296 } 297 mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); 298 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 299 "Defer removing snapshot surface in %d", delayRemovalTime); 300 } 301 hasImeSurface()302 protected abstract boolean hasImeSurface(); 303 304 @CallSuper removeImmediately()305 protected void removeImmediately() { 306 mRemoveExecutor.removeCallbacks(mScheduledRunnable); 307 mRecordManager.onRecordRemoved(this, mTaskId); 308 } 309 } 310 311 static class StartingWindowRecordManager { 312 private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); 313 private final SparseArray<StartingWindowRecord> mStartingWindowRecords = 314 new SparseArray<>(); 315 clearAllWindows()316 void clearAllWindows() { 317 final int taskSize = mStartingWindowRecords.size(); 318 final int[] taskIds = new int[taskSize]; 319 for (int i = taskSize - 1; i >= 0; --i) { 320 taskIds[i] = mStartingWindowRecords.keyAt(i); 321 } 322 for (int i = taskSize - 1; i >= 0; --i) { 323 removeWindow(taskIds[i]); 324 } 325 } 326 addRecord(int taskId, StartingWindowRecord record)327 void addRecord(int taskId, StartingWindowRecord record) { 328 final StartingWindowRecord original = mStartingWindowRecords.get(taskId); 329 if (original != null) { 330 mTmpRemovalInfo.taskId = taskId; 331 original.removeIfPossible(mTmpRemovalInfo, true /* immediately */); 332 } 333 mStartingWindowRecords.put(taskId, record); 334 } 335 removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately)336 void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) { 337 final int taskId = removeInfo.taskId; 338 final StartingWindowRecord record = mStartingWindowRecords.get(taskId); 339 if (record != null) { 340 final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately); 341 if (canRemoveRecord) { 342 mStartingWindowRecords.remove(taskId); 343 } 344 } 345 } 346 removeWindow(int taskId)347 void removeWindow(int taskId) { 348 mTmpRemovalInfo.taskId = taskId; 349 removeWindow(mTmpRemovalInfo, true/* immediately */); 350 } 351 onRecordRemoved(@onNull StartingWindowRecord record, int taskId)352 void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) { 353 final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId); 354 if (currentRecord == record) { 355 mStartingWindowRecords.remove(taskId); 356 } 357 } 358 getRecord(int taskId)359 StartingWindowRecord getRecord(int taskId) { 360 return mStartingWindowRecords.get(taskId); 361 } 362 363 @VisibleForTesting recordSize()364 int recordSize() { 365 return mStartingWindowRecords.size(); 366 } 367 } 368 } 369