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