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