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