• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
25 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
26 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
27 
28 import android.app.ActivityManager.RunningTaskInfo;
29 import android.app.TaskInfo;
30 import android.content.Context;
31 import android.graphics.Color;
32 import android.os.IBinder;
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, IBinder appToken)155     public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
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 (isSplashScreenType(suggestionType)) {
163                 mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
164                         suggestionType);
165             } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
166                 final TaskSnapshot snapshot = windowInfo.taskSnapshot;
167                 mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
168                         snapshot);
169             }
170             if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
171                 int taskId = runningTaskInfo.taskId;
172                 int color = mStartingSurfaceDrawer
173                         .getStartingWindowBackgroundColorForTask(taskId);
174                 if (color != Color.TRANSPARENT) {
175                     synchronized (mTaskBackgroundColors) {
176                         mTaskBackgroundColors.append(taskId, color);
177                     }
178                 }
179                 if (mTaskLaunchingCallback != null && isSplashScreenType(suggestionType)) {
180                     mTaskLaunchingCallback.accept(taskId, suggestionType, color);
181                 }
182             }
183 
184             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
185         });
186     }
187 
isSplashScreenType(@tartingWindowType int suggestionType)188     private static boolean isSplashScreenType(@StartingWindowType int suggestionType) {
189         return suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
190                 || suggestionType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
191                 || suggestionType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
192     }
193 
copySplashScreenView(int taskId)194     public void copySplashScreenView(int taskId) {
195         mSplashScreenExecutor.execute(() -> {
196             mStartingSurfaceDrawer.copySplashScreenView(taskId);
197         });
198     }
199 
200     /**
201      * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int)
202      */
onAppSplashScreenViewRemoved(int taskId)203     public void onAppSplashScreenViewRemoved(int taskId) {
204         mSplashScreenExecutor.execute(
205                 () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId));
206     }
207 
208     /**
209      * Called when the IME has drawn on the organized task.
210      */
onImeDrawnOnTask(int taskId)211     public void onImeDrawnOnTask(int taskId) {
212         mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId));
213     }
214 
215     /**
216      * Called when the content of a task is ready to show, starting window can be removed.
217      */
removeStartingWindow(StartingWindowRemovalInfo removalInfo)218     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
219         mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
220                 removalInfo));
221         mSplashScreenExecutor.executeDelayed(() -> {
222             synchronized (mTaskBackgroundColors) {
223                 mTaskBackgroundColors.delete(removalInfo.taskId);
224             }
225         }, TASK_BG_COLOR_RETAIN_TIME_MS);
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