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 package com.android.wm.shell.startingsurface; 17 18 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 19 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN; 20 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; 21 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; 22 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; 23 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; 24 25 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; 26 27 import android.app.ActivityManager.RunningTaskInfo; 28 import android.app.TaskInfo; 29 import android.content.Context; 30 import android.graphics.Color; 31 import android.os.IBinder; 32 import android.os.Trace; 33 import android.util.SparseIntArray; 34 import android.window.StartingWindowInfo; 35 import android.window.StartingWindowInfo.StartingWindowType; 36 import android.window.StartingWindowRemovalInfo; 37 import android.window.TaskOrganizer; 38 import android.window.TaskSnapshot; 39 40 import androidx.annotation.BinderThread; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.util.function.TriConsumer; 44 import com.android.launcher3.icons.IconProvider; 45 import com.android.wm.shell.common.RemoteCallable; 46 import com.android.wm.shell.common.ShellExecutor; 47 import com.android.wm.shell.common.SingleInstanceRemoteListener; 48 import com.android.wm.shell.common.TransactionPool; 49 50 /** 51 * Implementation to draw the starting window to an application, and remove the starting window 52 * until the application displays its own window. 53 * 54 * When receive {@link TaskOrganizer#addStartingWindow} callback, use this class to create a 55 * starting window and attached to the Task, then when the Task want to remove the starting window, 56 * the TaskOrganizer will receive {@link TaskOrganizer#removeStartingWindow} callback then use this 57 * class to remove the starting window of the Task. 58 * Besides add/remove starting window, There is an API #setStartingWindowListener to register 59 * a callback when starting window is about to create which let the registerer knows the next 60 * starting window's type. 61 * So far all classes in this package is an enclose system so there is no interact with other shell 62 * component, all the methods must be executed in splash screen thread or the thread used in 63 * constructor to keep everything synchronized. 64 * @hide 65 */ 66 public class StartingWindowController implements RemoteCallable<StartingWindowController> { 67 private static final String TAG = StartingWindowController.class.getSimpleName(); 68 69 public static final boolean DEBUG_SPLASH_SCREEN = false; 70 public static final boolean DEBUG_TASK_SNAPSHOT = false; 71 72 private static final long TASK_BG_COLOR_RETAIN_TIME_MS = 5000; 73 74 private final StartingSurfaceDrawer mStartingSurfaceDrawer; 75 private final StartingWindowTypeAlgorithm mStartingWindowTypeAlgorithm; 76 77 private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback; 78 private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); 79 private final Context mContext; 80 private final ShellExecutor mSplashScreenExecutor; 81 /** 82 * Need guarded because it has exposed to StartingSurface 83 */ 84 @GuardedBy("mTaskBackgroundColors") 85 private final SparseIntArray mTaskBackgroundColors = new SparseIntArray(); 86 StartingWindowController(Context context, ShellExecutor splashScreenExecutor, StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider, TransactionPool pool)87 public StartingWindowController(Context context, ShellExecutor splashScreenExecutor, 88 StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider, 89 TransactionPool pool) { 90 mContext = context; 91 mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, 92 iconProvider, pool); 93 mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm; 94 mSplashScreenExecutor = splashScreenExecutor; 95 } 96 97 /** 98 * Provide the implementation for Shell Module. 99 */ asStartingSurface()100 public StartingSurface asStartingSurface() { 101 return mImpl; 102 } 103 104 @Override getContext()105 public Context getContext() { 106 return mContext; 107 } 108 109 @Override getRemoteCallExecutor()110 public ShellExecutor getRemoteCallExecutor() { 111 return mSplashScreenExecutor; 112 } 113 114 /* 115 * Registers the starting window listener. 116 * 117 * @param listener The callback when need a starting window. 118 */ setStartingWindowListener(TriConsumer<Integer, Integer, Integer> listener)119 void setStartingWindowListener(TriConsumer<Integer, Integer, Integer> listener) { 120 mTaskLaunchingCallback = listener; 121 } 122 123 /** 124 * Called when a task need a starting window. 125 */ addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken)126 public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { 127 mSplashScreenExecutor.execute(() -> { 128 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow"); 129 130 final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType( 131 windowInfo); 132 final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; 133 if (isSplashScreenType(suggestionType)) { 134 mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken, 135 suggestionType); 136 } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { 137 final TaskSnapshot snapshot = windowInfo.taskSnapshot; 138 mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, 139 snapshot); 140 } 141 if (suggestionType != STARTING_WINDOW_TYPE_NONE) { 142 int taskId = runningTaskInfo.taskId; 143 int color = mStartingSurfaceDrawer 144 .getStartingWindowBackgroundColorForTask(taskId); 145 if (color != Color.TRANSPARENT) { 146 synchronized (mTaskBackgroundColors) { 147 mTaskBackgroundColors.append(taskId, color); 148 } 149 } 150 if (mTaskLaunchingCallback != null && isSplashScreenType(suggestionType)) { 151 mTaskLaunchingCallback.accept(taskId, suggestionType, color); 152 } 153 } 154 155 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 156 }); 157 } 158 isSplashScreenType(@tartingWindowType int suggestionType)159 private static boolean isSplashScreenType(@StartingWindowType int suggestionType) { 160 return suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN 161 || suggestionType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN 162 || suggestionType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; 163 } 164 copySplashScreenView(int taskId)165 public void copySplashScreenView(int taskId) { 166 mSplashScreenExecutor.execute(() -> { 167 mStartingSurfaceDrawer.copySplashScreenView(taskId); 168 }); 169 } 170 171 /** 172 * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int) 173 */ onAppSplashScreenViewRemoved(int taskId)174 public void onAppSplashScreenViewRemoved(int taskId) { 175 mSplashScreenExecutor.execute( 176 () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId)); 177 } 178 179 /** 180 * Called when the IME has drawn on the organized task. 181 */ onImeDrawnOnTask(int taskId)182 public void onImeDrawnOnTask(int taskId) { 183 mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId)); 184 } 185 186 /** 187 * Called when the content of a task is ready to show, starting window can be removed. 188 */ removeStartingWindow(StartingWindowRemovalInfo removalInfo)189 public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { 190 mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow( 191 removalInfo)); 192 mSplashScreenExecutor.executeDelayed(() -> { 193 synchronized (mTaskBackgroundColors) { 194 mTaskBackgroundColors.delete(removalInfo.taskId); 195 } 196 }, TASK_BG_COLOR_RETAIN_TIME_MS); 197 } 198 199 /** 200 * Clear all starting window immediately, called this method when releasing the task organizer. 201 */ clearAllWindows()202 public void clearAllWindows() { 203 mSplashScreenExecutor.execute(() -> { 204 mStartingSurfaceDrawer.clearAllWindows(); 205 synchronized (mTaskBackgroundColors) { 206 mTaskBackgroundColors.clear(); 207 } 208 }); 209 } 210 211 /** 212 * The interface for calls from outside the Shell, within the host process. 213 */ 214 private class StartingSurfaceImpl implements StartingSurface { 215 private IStartingWindowImpl mIStartingWindow; 216 217 @Override createExternalInterface()218 public IStartingWindowImpl createExternalInterface() { 219 if (mIStartingWindow != null) { 220 mIStartingWindow.invalidate(); 221 } 222 mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this); 223 return mIStartingWindow; 224 } 225 226 @Override getBackgroundColor(TaskInfo taskInfo)227 public int getBackgroundColor(TaskInfo taskInfo) { 228 synchronized (mTaskBackgroundColors) { 229 final int index = mTaskBackgroundColors.indexOfKey(taskInfo.taskId); 230 if (index >= 0) { 231 return mTaskBackgroundColors.valueAt(index); 232 } 233 } 234 final int color = mStartingSurfaceDrawer.estimateTaskBackgroundColor(taskInfo); 235 return color != Color.TRANSPARENT 236 ? color : SplashscreenContentDrawer.getSystemBGColor(); 237 } 238 239 @Override setSysuiProxy(SysuiProxy proxy)240 public void setSysuiProxy(SysuiProxy proxy) { 241 mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.setSysuiProxy(proxy)); 242 } 243 } 244 245 /** 246 * The interface for calls from outside the host process. 247 */ 248 @BinderThread 249 private static class IStartingWindowImpl extends IStartingWindow.Stub { 250 private StartingWindowController mController; 251 private SingleInstanceRemoteListener<StartingWindowController, 252 IStartingWindowListener> mListener; 253 private final TriConsumer<Integer, Integer, Integer> mStartingWindowListener = 254 (taskId, supportedType, startingWindowBackgroundColor) -> { 255 mListener.call(l -> l.onTaskLaunching(taskId, supportedType, 256 startingWindowBackgroundColor)); 257 }; 258 IStartingWindowImpl(StartingWindowController controller)259 public IStartingWindowImpl(StartingWindowController controller) { 260 mController = controller; 261 mListener = new SingleInstanceRemoteListener<>(controller, 262 c -> c.setStartingWindowListener(mStartingWindowListener), 263 c -> c.setStartingWindowListener(null)); 264 } 265 266 /** 267 * Invalidates this instance, preventing future calls from updating the controller. 268 */ invalidate()269 void invalidate() { 270 mController = null; 271 } 272 273 @Override setStartingWindowListener(IStartingWindowListener listener)274 public void setStartingWindowListener(IStartingWindowListener listener) { 275 executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener", 276 (controller) -> { 277 if (listener != null) { 278 mListener.register(listener); 279 } else { 280 mListener.unregister(); 281 } 282 }); 283 } 284 } 285 } 286