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