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