• 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 
17 package com.android.wm.shell.startingsurface;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
22 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
23 
24 import android.annotation.CallSuper;
25 import android.app.TaskInfo;
26 import android.app.WindowConfiguration;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.graphics.Color;
30 import android.hardware.display.DisplayManager;
31 import android.util.SparseArray;
32 import android.view.IWindow;
33 import android.view.SurfaceControl;
34 import android.view.SurfaceSession;
35 import android.view.WindowManager;
36 import android.view.WindowlessWindowManager;
37 import android.window.SplashScreenView;
38 import android.window.StartingWindowInfo;
39 import android.window.StartingWindowInfo.StartingWindowType;
40 import android.window.StartingWindowRemovalInfo;
41 import android.window.TaskSnapshot;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.protolog.common.ProtoLog;
45 import com.android.launcher3.icons.IconProvider;
46 import com.android.wm.shell.common.ShellExecutor;
47 import com.android.wm.shell.common.TransactionPool;
48 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
49 import com.android.wm.shell.protolog.ShellProtoLogGroup;
50 
51 /**
52  * A class which able to draw splash screen or snapshot as the starting window for a task.
53  */
54 @ShellSplashscreenThread
55 public class StartingSurfaceDrawer {
56 
57     private final ShellExecutor mSplashScreenExecutor;
58     @VisibleForTesting
59     final SplashscreenContentDrawer mSplashscreenContentDrawer;
60     @VisibleForTesting
61     final SplashscreenWindowCreator mSplashscreenWindowCreator;
62     private final SnapshotWindowCreator mSnapshotWindowCreator;
63     private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
64     private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
65 
66     @VisibleForTesting
67     final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
68     // Windowless surface could co-exist with starting window in a task.
69     @VisibleForTesting
70     final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
71     /**
72      * @param splashScreenExecutor The thread used to control add and remove starting window.
73      */
StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, IconProvider iconProvider, TransactionPool pool)74     public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
75             IconProvider iconProvider, TransactionPool pool) {
76         mSplashScreenExecutor = splashScreenExecutor;
77         final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
78         mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
79         displayManager.getDisplay(DEFAULT_DISPLAY);
80 
81         mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
82                 context, splashScreenExecutor, displayManager, mWindowRecords);
83         mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
84                 mWindowRecords);
85         mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
86                 mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
87                 mWindowlessRecords, pool);
88         mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
89                 mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
90     }
91 
setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy)92     void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
93         mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
94         mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
95     }
96 
97     /**
98      * Called when a task need a splash screen starting window.
99      *
100      * @param suggestType The suggestion type to draw the splash screen.
101      */
addSplashScreenStartingWindow(StartingWindowInfo windowInfo, @StartingWindowType int suggestType)102     void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
103             @StartingWindowType int suggestType) {
104         mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
105     }
106 
getStartingWindowBackgroundColorForTask(int taskId)107     int getStartingWindowBackgroundColorForTask(int taskId) {
108         final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
109         if (startingWindowRecord == null) {
110             return Color.TRANSPARENT;
111         }
112         return startingWindowRecord.getBGColor();
113     }
114 
estimateTaskBackgroundColor(TaskInfo taskInfo)115     int estimateTaskBackgroundColor(TaskInfo taskInfo) {
116         return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
117     }
118 
119     /**
120      * Called when a task need a snapshot starting window.
121      */
makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot)122     void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
123         mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
124     }
125 
126     /**
127      * Called when the content of a task is ready to show, starting window can be removed.
128      */
removeStartingWindow(StartingWindowRemovalInfo removalInfo)129     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
130         if (removalInfo.windowlessSurface) {
131             mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
132         } else {
133             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
134                     "Task start finish, remove starting surface for task: %d",
135                     removalInfo.taskId);
136             mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
137         }
138     }
139 
140     /**
141      * Create a windowless starting surface and attach to the root surface.
142      */
addWindowlessStartingSurface(StartingWindowInfo windowInfo)143     void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
144         if (windowInfo.taskSnapshot != null) {
145             mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
146                     windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
147         } else {
148             mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
149                     windowInfo, windowInfo.rootSurface);
150         }
151     }
152 
153     /**
154      * Clear all starting windows immediately.
155      */
clearAllWindows()156     public void clearAllWindows() {
157         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
158                 "Clear all starting windows immediately");
159         mWindowRecords.clearAllWindows();
160         mWindowlessRecords.clearAllWindows();
161     }
162 
163     /**
164      * Called when the Task wants to copy the splash screen.
165      */
copySplashScreenView(int taskId)166     public void copySplashScreenView(int taskId) {
167         mSplashscreenWindowCreator.copySplashScreenView(taskId);
168     }
169 
170     /**
171      * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
172      * or when the Activity is clean up.
173      *
174      * @param taskId The Task id on which the splash screen was attached
175      */
onAppSplashScreenViewRemoved(int taskId)176     public void onAppSplashScreenViewRemoved(int taskId) {
177         mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
178     }
179 
onImeDrawnOnTask(int taskId)180     void onImeDrawnOnTask(int taskId) {
181         onImeDrawnOnTask(mWindowRecords, taskId);
182         onImeDrawnOnTask(mWindowlessRecords, taskId);
183     }
184 
onImeDrawnOnTask(StartingWindowRecordManager records, int taskId)185     private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
186         final StartingSurfaceDrawer.StartingWindowRecord sRecord =
187                 records.getRecord(taskId);
188         final SnapshotRecord record = sRecord instanceof SnapshotRecord
189                 ? (SnapshotRecord) sRecord : null;
190         if (record != null && record.hasImeSurface()) {
191             records.removeWindow(taskId);
192         }
193     }
194 
195     static class WindowlessStartingWindow extends WindowlessWindowManager {
196         SurfaceControl mChildSurface;
197 
WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface)198         WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
199             super(c, rootSurface, null /* hostInputToken */);
200         }
201 
202         @Override
getParentSurface(IWindow window, WindowManager.LayoutParams attrs)203         protected SurfaceControl getParentSurface(IWindow window,
204                 WindowManager.LayoutParams attrs) {
205             final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
206                     .setContainerLayer()
207                     .setName("Windowless window")
208                     .setHidden(false)
209                     .setParent(mRootSurface)
210                     .setCallsite("WindowlessStartingWindow#attachToParentSurface");
211             mChildSurface = builder.build();
212             try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
213                 t.setLayer(mChildSurface, Integer.MAX_VALUE);
214                 t.apply();
215             }
216             return mChildSurface;
217         }
218     }
219     abstract static class StartingWindowRecord {
220         protected int mBGColor;
221 
222         /**
223          * Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible.
224          * @param info The removal info sent from the task organizer controller in the WM core.
225          * @param immediately {@code true} means removing the starting window immediately,
226          *                    {@code false} otherwise.
227          * @return {@code true} means {@link StartingWindowRecordManager} can safely remove the
228          *         record itself. {@code false} means {@link StartingWindowRecordManager} requires
229          *         to manage the record reference and remove it later.
230          */
removeIfPossible(StartingWindowRemovalInfo info, boolean immediately)231         abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
getBGColor()232         int getBGColor() {
233             return mBGColor;
234         }
235     }
236 
237     abstract static class SnapshotRecord extends StartingWindowRecord {
238         private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
239         /**
240          * The max delay time in milliseconds for removing the task snapshot window with IME
241          * visible.
242          * Ideally the delay time will be shorter when receiving
243          * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
244          */
245         private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
246 
247         /**
248          * The max delay time in milliseconds for removing the task snapshot window with IME
249          * visible after the fixed rotation finished.
250          * Ideally the delay time will be shorter when receiving
251          * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
252          */
253         private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000;
254 
255         private final Runnable mScheduledRunnable = this::removeImmediately;
256 
257         @WindowConfiguration.ActivityType protected final int mActivityType;
258         protected final ShellExecutor mRemoveExecutor;
259         private final int mTaskId;
260         private final StartingWindowRecordManager mRecordManager;
261 
SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId, StartingWindowRecordManager recordManager)262         SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId,
263                 StartingWindowRecordManager recordManager) {
264             mActivityType = activityType;
265             mRemoveExecutor = removeExecutor;
266             mTaskId = taskId;
267             mRecordManager = recordManager;
268         }
269 
270         @Override
removeIfPossible(StartingWindowRemovalInfo info, boolean immediately)271         public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
272             if (immediately) {
273                 removeImmediately();
274             } else {
275                 scheduleRemove(info.deferRemoveForImeMode);
276                 return false;
277             }
278             return true;
279         }
280 
scheduleRemove(@tartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode)281         void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
282             // Show the latest content as soon as possible for unlocking to home.
283             if (mActivityType == ACTIVITY_TYPE_HOME) {
284                 removeImmediately();
285                 return;
286             }
287             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
288             final long delayRemovalTime;
289             switch (deferRemoveForImeMode) {
290                 case DEFER_MODE_ROTATION:
291                     delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION;
292                     break;
293                 case DEFER_MODE_NORMAL:
294                     delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE;
295                     break;
296                 default:
297                     delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL;
298             }
299             mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
300             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
301                     "Defer removing snapshot surface in %d", delayRemovalTime);
302         }
303 
hasImeSurface()304         protected abstract boolean hasImeSurface();
305 
306         @CallSuper
removeImmediately()307         protected void removeImmediately() {
308             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
309             mRecordManager.onRecordRemoved(mTaskId);
310         }
311     }
312 
313     static class StartingWindowRecordManager {
314         private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
315         private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
316                 new SparseArray<>();
317 
clearAllWindows()318         void clearAllWindows() {
319             final int taskSize = mStartingWindowRecords.size();
320             final int[] taskIds = new int[taskSize];
321             for (int i = taskSize - 1; i >= 0; --i) {
322                 taskIds[i] = mStartingWindowRecords.keyAt(i);
323             }
324             for (int i = taskSize - 1; i >= 0; --i) {
325                 removeWindow(taskIds[i]);
326             }
327         }
328 
addRecord(int taskId, StartingWindowRecord record)329         void addRecord(int taskId, StartingWindowRecord record) {
330             mStartingWindowRecords.put(taskId, record);
331         }
332 
removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately)333         void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
334             final int taskId = removeInfo.taskId;
335             final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
336             if (record != null) {
337                 final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately);
338                 if (canRemoveRecord) {
339                     mStartingWindowRecords.remove(taskId);
340                 }
341             }
342         }
343 
removeWindow(int taskId)344         void removeWindow(int taskId) {
345             mTmpRemovalInfo.taskId = taskId;
346             removeWindow(mTmpRemovalInfo, true/* immediately */);
347         }
348 
onRecordRemoved(int taskId)349         void onRecordRemoved(int taskId) {
350             mStartingWindowRecords.remove(taskId);
351         }
352 
getRecord(int taskId)353         StartingWindowRecord getRecord(int taskId) {
354             return mStartingWindowRecords.get(taskId);
355         }
356 
357         @VisibleForTesting
recordSize()358         int recordSize() {
359             return mStartingWindowRecords.size();
360         }
361     }
362 }
363