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