• 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 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