• 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.content.Context.CONTEXT_RESTRICTED;
20 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
21 import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
22 import static android.view.Display.DEFAULT_DISPLAY;
23 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
24 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
25 
26 import android.annotation.Nullable;
27 import android.app.ActivityManager.RunningTaskInfo;
28 import android.app.ActivityTaskManager;
29 import android.app.ActivityThread;
30 import android.app.TaskInfo;
31 import android.content.Context;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.IPackageManager;
34 import android.content.pm.PackageManager;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.content.res.TypedArray;
38 import android.graphics.Color;
39 import android.graphics.PixelFormat;
40 import android.hardware.display.DisplayManager;
41 import android.os.IBinder;
42 import android.os.RemoteCallback;
43 import android.os.RemoteException;
44 import android.os.SystemClock;
45 import android.os.Trace;
46 import android.os.UserHandle;
47 import android.util.Slog;
48 import android.util.SparseArray;
49 import android.view.Choreographer;
50 import android.view.Display;
51 import android.view.SurfaceControlViewHost;
52 import android.view.View;
53 import android.view.WindowInsetsController;
54 import android.view.WindowManager;
55 import android.view.WindowManagerGlobal;
56 import android.widget.FrameLayout;
57 import android.window.SplashScreenView;
58 import android.window.SplashScreenView.SplashScreenViewParcelable;
59 import android.window.StartingWindowInfo;
60 import android.window.StartingWindowInfo.StartingWindowType;
61 import android.window.StartingWindowRemovalInfo;
62 import android.window.TaskSnapshot;
63 
64 import com.android.internal.R;
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.internal.protolog.common.ProtoLog;
67 import com.android.internal.util.ContrastColorUtil;
68 import com.android.launcher3.icons.IconProvider;
69 import com.android.wm.shell.common.ShellExecutor;
70 import com.android.wm.shell.common.TransactionPool;
71 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
72 import com.android.wm.shell.protolog.ShellProtoLogGroup;
73 
74 import java.util.function.Supplier;
75 
76 /**
77  * A class which able to draw splash screen or snapshot as the starting window for a task.
78  *
79  * In order to speed up, there will use two threads to creating a splash screen in parallel.
80  * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
81  * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
82  * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
83  * can synchronize on each frame.
84  *
85  * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
86  * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
87  * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
88  * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
89  * quickly.
90  *
91  * So basically we are using the spare time to prepare the SplashScreenView while splash screen
92  * thread is waiting for
93  * 1. WindowManager#addView(binder call to WM),
94  * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
95  * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
96  * always happen before #draw).
97  * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
98  * splash-screen background tread can make they execute in parallel, which ensure it is faster then
99  * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
100  *
101  * Here is the sequence to compare the difference between using single and two thread.
102  *
103  * Single thread:
104  * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
105  * -> draw -> AdaptiveIconDrawable#draw
106  *
107  * Two threads:
108  * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
109  * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
110  * directly).
111  */
112 @ShellSplashscreenThread
113 public class StartingSurfaceDrawer {
114     private static final String TAG = StartingWindowController.TAG;
115 
116     private final Context mContext;
117     private final DisplayManager mDisplayManager;
118     private final ShellExecutor mSplashScreenExecutor;
119     @VisibleForTesting
120     final SplashscreenContentDrawer mSplashscreenContentDrawer;
121     private Choreographer mChoreographer;
122     private final WindowManagerGlobal mWindowManagerGlobal;
123     private StartingSurface.SysuiProxy mSysuiProxy;
124     private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
125 
126     private static final int LIGHT_BARS_MASK =
127             WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
128                     | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
129     /**
130      * The minimum duration during which the splash screen is shown when the splash screen icon is
131      * animated.
132      */
133     static final long MINIMAL_ANIMATION_DURATION = 400L;
134 
135     /**
136      * Allow the icon style splash screen to be displayed for longer to give time for the animation
137      * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
138      * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
139      */
140     static final long TIME_WINDOW_DURATION = 100L;
141 
142     /**
143      * The maximum duration during which the splash screen will be shown if the application is ready
144      * to show before the icon animation finishes.
145      */
146     static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
147 
148     /**
149      * @param splashScreenExecutor The thread used to control add and remove starting window.
150      */
StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, IconProvider iconProvider, TransactionPool pool)151     public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
152             IconProvider iconProvider, TransactionPool pool) {
153         mContext = context;
154         mDisplayManager = mContext.getSystemService(DisplayManager.class);
155         mSplashScreenExecutor = splashScreenExecutor;
156         mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
157         mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
158         mWindowManagerGlobal = WindowManagerGlobal.getInstance();
159         mDisplayManager.getDisplay(DEFAULT_DISPLAY);
160     }
161 
162     @VisibleForTesting
163     final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
164 
165     /**
166      * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
167      * rendered and that have not yet been removed by their client.
168      */
169     private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
170             new SparseArray<>(1);
171 
getDisplay(int displayId)172     private Display getDisplay(int displayId) {
173         return mDisplayManager.getDisplay(displayId);
174     }
175 
getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo)176     int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
177         return splashScreenThemeResId != 0
178                 ? splashScreenThemeResId
179                 : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
180                         : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
181     }
182 
setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy)183     void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
184         mSysuiProxy = sysuiProxy;
185     }
186 
187     /**
188      * Called when a task need a splash screen starting window.
189      *
190      * @param suggestType The suggestion type to draw the splash screen.
191      */
addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken, @StartingWindowType int suggestType)192     void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
193             @StartingWindowType int suggestType) {
194         final RunningTaskInfo taskInfo = windowInfo.taskInfo;
195         final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
196                 ? windowInfo.targetActivityInfo
197                 : taskInfo.topActivityInfo;
198         if (activityInfo == null || activityInfo.packageName == null) {
199             return;
200         }
201 
202         final int displayId = taskInfo.displayId;
203         final int taskId = taskInfo.taskId;
204 
205         // replace with the default theme if the application didn't set
206         final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
207         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
208                 "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
209                 activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
210         final Display display = getDisplay(displayId);
211         if (display == null) {
212             // Can't show splash screen on requested display, so skip showing at all.
213             return;
214         }
215         Context context = displayId == DEFAULT_DISPLAY
216                 ? mContext : mContext.createDisplayContext(display);
217         if (context == null) {
218             return;
219         }
220         if (theme != context.getThemeResId()) {
221             try {
222                 context = context.createPackageContextAsUser(activityInfo.packageName,
223                         CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
224                 context.setTheme(theme);
225             } catch (PackageManager.NameNotFoundException e) {
226                 Slog.w(TAG, "Failed creating package context with package name "
227                         + activityInfo.packageName + " for user " + taskInfo.userId, e);
228                 return;
229             }
230         }
231 
232         final Configuration taskConfig = taskInfo.getConfiguration();
233         if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
234             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
235                     "addSplashScreen: creating context based on task Configuration %s",
236                     taskConfig);
237             final Context overrideContext = context.createConfigurationContext(taskConfig);
238             overrideContext.setTheme(theme);
239             final TypedArray typedArray = overrideContext.obtainStyledAttributes(
240                     com.android.internal.R.styleable.Window);
241             final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
242             try {
243                 if (resId != 0 && overrideContext.getDrawable(resId) != null) {
244                     // We want to use the windowBackground for the override context if it is
245                     // available, otherwise we use the default one to make sure a themed starting
246                     // window is displayed for the app.
247                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
248                             "addSplashScreen: apply overrideConfig %s",
249                             taskConfig);
250                     context = overrideContext;
251                 }
252             } catch (Resources.NotFoundException e) {
253                 Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
254                         + taskId, e);
255                 return;
256             }
257             typedArray.recycle();
258         }
259 
260         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
261                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
262         params.setFitInsetsSides(0);
263         params.setFitInsetsTypes(0);
264         params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
265                 ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
266         int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
267                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
268                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
269         final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
270         if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
271             windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
272         }
273         if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
274             if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
275                 windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
276             }
277         } else {
278             windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
279         }
280         params.layoutInDisplayCutoutMode = a.getInt(
281                 R.styleable.Window_windowLayoutInDisplayCutoutMode,
282                 params.layoutInDisplayCutoutMode);
283         params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
284         a.recycle();
285 
286         // Assumes it's safe to show starting windows of launched apps while
287         // the keyguard is being hidden. This is okay because starting windows never show
288         // secret information.
289         // TODO(b/113840485): Occluded may not only happen on default display
290         if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
291             windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
292         }
293 
294         // Force the window flags: this is a fake window, so it is not really
295         // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
296         // flag because we do know that the next window will take input
297         // focus, so we want to get the IME window up on top of us right away.
298         // Touches will only pass through to the host activity window and will be blocked from
299         // passing to any other windows.
300         windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
301                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
302                 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
303         params.flags = windowFlags;
304         params.token = appToken;
305         params.packageName = activityInfo.packageName;
306         params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
307 
308         if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
309             params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
310         }
311 
312         params.setTitle("Splash Screen " + activityInfo.packageName);
313 
314         // TODO(b/173975965) tracking performance
315         // Prepare the splash screen content view on splash screen worker thread in parallel, so the
316         // content view won't be blocked by binder call like addWindow and relayout.
317         // 1. Trigger splash screen worker thread to create SplashScreenView before/while
318         // Session#addWindow.
319         // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
320         // traversal, which will call Session#relayout on splash screen thread.
321         // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
322         // the same time the splash screen thread should be executing Session#relayout. Blocking the
323         // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
324 
325         // Record whether create splash screen view success, notify to current thread after
326         // create splash screen view finished.
327         final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
328         final FrameLayout rootLayout = new FrameLayout(
329                 mSplashscreenContentDrawer.createViewContextWrapper(context));
330         rootLayout.setPadding(0, 0, 0, 0);
331         rootLayout.setFitsSystemWindows(false);
332         final Runnable setViewSynchronized = () -> {
333             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
334             // waiting for setContentView before relayoutWindow
335             SplashScreenView contentView = viewSupplier.get();
336             final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
337             // If record == null, either the starting window added fail or removed already.
338             // Do not add this view if the token is mismatch.
339             if (record != null && appToken == record.mAppToken) {
340                 // if view == null then creation of content view was failed.
341                 if (contentView != null) {
342                     try {
343                         rootLayout.addView(contentView);
344                     } catch (RuntimeException e) {
345                         Slog.w(TAG, "failed set content view to starting window "
346                                 + "at taskId: " + taskId, e);
347                         contentView = null;
348                     }
349                 }
350                 record.setSplashScreenView(contentView);
351             }
352             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
353         };
354         if (mSysuiProxy != null) {
355             mSysuiProxy.requestTopUi(true, TAG);
356         }
357         mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
358                 viewSupplier::setView, viewSupplier::setUiThreadInitTask);
359         try {
360             if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
361                 // We use the splash screen worker thread to create SplashScreenView while adding
362                 // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
363                 // And since Choreographer#doFrame won't happen immediately after adding the window,
364                 // if the view is not added to the PhoneWindow on the first #doFrame, the view will
365                 // not be rendered on the first frame. So here we need to synchronize the view on
366                 // the window before first round relayoutWindow, which will happen after insets
367                 // animation.
368                 mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
369                 final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
370                 record.parseAppSystemBarColor(context);
371                 // Block until we get the background color.
372                 final SplashScreenView contentView = viewSupplier.get();
373                 if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
374                     contentView.addOnAttachStateChangeListener(
375                             new View.OnAttachStateChangeListener() {
376                                 @Override
377                                 public void onViewAttachedToWindow(View v) {
378                                     final int lightBarAppearance = ContrastColorUtil.isColorLight(
379                                             contentView.getInitBackgroundColor())
380                                             ? LIGHT_BARS_MASK : 0;
381                                     contentView.getWindowInsetsController().setSystemBarsAppearance(
382                                             lightBarAppearance, LIGHT_BARS_MASK);
383                                 }
384 
385                                 @Override
386                                 public void onViewDetachedFromWindow(View v) {
387                                 }
388                             });
389                 }
390                 record.mBGColor = contentView.getInitBackgroundColor();
391             } else {
392                 // release the icon view host
393                 final SplashScreenView contentView = viewSupplier.get();
394                 if (contentView.getSurfaceHost() != null) {
395                     SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
396                 }
397             }
398         } catch (RuntimeException e) {
399             // don't crash if something else bad happens, for example a
400             // failure loading resources because we are loading from an app
401             // on external storage that has been unmounted.
402             Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
403         }
404     }
405 
getStartingWindowBackgroundColorForTask(int taskId)406     int getStartingWindowBackgroundColorForTask(int taskId) {
407         final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
408         if (startingWindowRecord == null) {
409             return Color.TRANSPARENT;
410         }
411         return startingWindowRecord.mBGColor;
412     }
413 
414     private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
415         private SplashScreenView mView;
416         private boolean mIsViewSet;
417         private Runnable mUiThreadInitTask;
setView(SplashScreenView view)418         void setView(SplashScreenView view) {
419             synchronized (this) {
420                 mView = view;
421                 mIsViewSet = true;
422                 notify();
423             }
424         }
425 
setUiThreadInitTask(Runnable initTask)426         void setUiThreadInitTask(Runnable initTask) {
427             synchronized (this) {
428                 mUiThreadInitTask = initTask;
429             }
430         }
431 
432         @Override
433         @Nullable
get()434         public SplashScreenView get() {
435             synchronized (this) {
436                 while (!mIsViewSet) {
437                     try {
438                         wait();
439                     } catch (InterruptedException ignored) {
440                     }
441                 }
442                 if (mUiThreadInitTask != null) {
443                     mUiThreadInitTask.run();
444                     mUiThreadInitTask = null;
445                 }
446                 return mView;
447             }
448         }
449     }
450 
estimateTaskBackgroundColor(TaskInfo taskInfo)451     int estimateTaskBackgroundColor(TaskInfo taskInfo) {
452         if (taskInfo.topActivityInfo == null) {
453             return Color.TRANSPARENT;
454         }
455         final ActivityInfo activityInfo = taskInfo.topActivityInfo;
456         final String packageName = activityInfo.packageName;
457         final int userId = taskInfo.userId;
458         final Context windowContext;
459         try {
460             windowContext = mContext.createPackageContextAsUser(
461                     packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
462         } catch (PackageManager.NameNotFoundException e) {
463             Slog.w(TAG, "Failed creating package context with package name "
464                     + packageName + " for user " + taskInfo.userId, e);
465             return Color.TRANSPARENT;
466         }
467         try {
468             final IPackageManager packageManager = ActivityThread.getPackageManager();
469             final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
470                     userId);
471             final int splashScreenThemeId = splashScreenThemeName != null
472                     ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
473                     : 0;
474 
475             final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
476 
477             if (theme != windowContext.getThemeResId()) {
478                 windowContext.setTheme(theme);
479             }
480             return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
481         } catch (RuntimeException | RemoteException e) {
482             Slog.w(TAG, "failed get starting window background color at taskId: "
483                     + taskInfo.taskId, e);
484         }
485         return Color.TRANSPARENT;
486     }
487 
488     /**
489      * Called when a task need a snapshot starting window.
490      */
makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken, TaskSnapshot snapshot)491     void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
492             TaskSnapshot snapshot) {
493         final int taskId = startingWindowInfo.taskInfo.taskId;
494         // Remove any existing starting window for this task before adding.
495         removeWindowNoAnimate(taskId);
496         final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
497                 snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
498         if (surface == null) {
499             return;
500         }
501         final StartingWindowRecord tView = new StartingWindowRecord(appToken,
502                 null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
503         mStartingWindowRecords.put(taskId, tView);
504     }
505 
506     /**
507      * Called when the content of a task is ready to show, starting window can be removed.
508      */
removeStartingWindow(StartingWindowRemovalInfo removalInfo)509     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
510         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
511                 "Task start finish, remove starting surface for task: %d",
512                 removalInfo.taskId);
513         removeWindowSynced(removalInfo, false /* immediately */);
514     }
515 
516     /**
517      * Clear all starting windows immediately.
518      */
clearAllWindows()519     public void clearAllWindows() {
520         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
521                 "Clear all starting windows immediately");
522         final int taskSize = mStartingWindowRecords.size();
523         final int[] taskIds = new int[taskSize];
524         for (int i = taskSize - 1; i >= 0; --i) {
525             taskIds[i] = mStartingWindowRecords.keyAt(i);
526         }
527         for (int i = taskSize - 1; i >= 0; --i) {
528             removeWindowNoAnimate(taskIds[i]);
529         }
530     }
531 
532     /**
533      * Called when the Task wants to copy the splash screen.
534      */
copySplashScreenView(int taskId)535     public void copySplashScreenView(int taskId) {
536         final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
537         SplashScreenViewParcelable parcelable;
538         SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
539         if (splashScreenView != null && splashScreenView.isCopyable()) {
540             parcelable = new SplashScreenViewParcelable(splashScreenView);
541             parcelable.setClientCallback(
542                     new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
543                             () -> onAppSplashScreenViewRemoved(taskId, false))));
544             splashScreenView.onCopied();
545             mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
546         } else {
547             parcelable = null;
548         }
549         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
550                 "Copying splash screen window view for task: %d with parcelable %b",
551                 taskId, parcelable != null);
552         ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
553     }
554 
555     /**
556      * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
557      * or when the Activity is clean up.
558      *
559      * @param taskId The Task id on which the splash screen was attached
560      */
onAppSplashScreenViewRemoved(int taskId)561     public void onAppSplashScreenViewRemoved(int taskId) {
562         onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
563     }
564 
565     /**
566      * @param fromServer If true, this means the removal was notified by the server. This is only
567      *                   used for debugging purposes.
568      * @see #onAppSplashScreenViewRemoved(int)
569      */
onAppSplashScreenViewRemoved(int taskId, boolean fromServer)570     private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
571         SurfaceControlViewHost viewHost =
572                 mAnimatedSplashScreenSurfaceHosts.get(taskId);
573         if (viewHost == null) {
574             return;
575         }
576         mAnimatedSplashScreenSurfaceHosts.remove(taskId);
577         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
578                 "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
579                 fromServer ? "Server cleaned up" : "App removed", taskId);
580         SplashScreenView.releaseIconHost(viewHost);
581     }
582 
addWindow(int taskId, IBinder appToken, View view, Display display, WindowManager.LayoutParams params, @StartingWindowType int suggestType)583     protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
584             WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
585         boolean shouldSaveView = true;
586         final Context context = view.getContext();
587         try {
588             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
589             mWindowManagerGlobal.addView(view, params, display,
590                     null /* parentWindow */, context.getUserId());
591         } catch (WindowManager.BadTokenException e) {
592             // ignore
593             Slog.w(TAG, appToken + " already running, starting window not displayed. "
594                     + e.getMessage());
595             shouldSaveView = false;
596         } finally {
597             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
598             if (view.getParent() == null) {
599                 Slog.w(TAG, "view not successfully added to wm, removing view");
600                 mWindowManagerGlobal.removeView(view, true /* immediate */);
601                 shouldSaveView = false;
602             }
603         }
604         if (shouldSaveView) {
605             removeWindowNoAnimate(taskId);
606             saveSplashScreenRecord(appToken, taskId, view, suggestType);
607         }
608         return shouldSaveView;
609     }
610 
611     @VisibleForTesting
saveSplashScreenRecord(IBinder appToken, int taskId, View view, @StartingWindowType int suggestType)612     void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
613             @StartingWindowType int suggestType) {
614         final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
615                 null/* TaskSnapshotWindow */, suggestType);
616         mStartingWindowRecords.put(taskId, tView);
617     }
618 
removeWindowNoAnimate(int taskId)619     private void removeWindowNoAnimate(int taskId) {
620         mTmpRemovalInfo.taskId = taskId;
621         removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
622     }
623 
onImeDrawnOnTask(int taskId)624     void onImeDrawnOnTask(int taskId) {
625         final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
626         if (record != null && record.mTaskSnapshotWindow != null
627                 && record.mTaskSnapshotWindow.hasImeSurface()) {
628             removeWindowNoAnimate(taskId);
629         }
630     }
631 
removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately)632     protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
633         final int taskId = removalInfo.taskId;
634         final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
635         if (record != null) {
636             if (record.mDecorView != null) {
637                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
638                         "Removing splash screen window for task: %d", taskId);
639                 if (record.mContentView != null) {
640                     record.clearSystemBarColor();
641                     if (immediately
642                             || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
643                         removeWindowInner(record.mDecorView, false);
644                     } else {
645                         if (removalInfo.playRevealAnimation) {
646                             mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
647                                     removalInfo.windowAnimationLeash, removalInfo.mainFrame,
648                                     () -> removeWindowInner(record.mDecorView, true),
649                                     record.mCreateTime, removalInfo.roundedCornerRadius);
650                         } else {
651                             // the SplashScreenView has been copied to client, hide the view to skip
652                             // default exit animation
653                             removeWindowInner(record.mDecorView, true);
654                         }
655                     }
656                 } else {
657                     // shouldn't happen
658                     Slog.e(TAG, "Found empty splash screen, remove!");
659                     removeWindowInner(record.mDecorView, false);
660                 }
661 
662             }
663             if (record.mTaskSnapshotWindow != null) {
664                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
665                         "Removing task snapshot window for %d", taskId);
666                 if (immediately) {
667                     record.mTaskSnapshotWindow.removeImmediately();
668                 } else {
669                     record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
670                 }
671             }
672             mStartingWindowRecords.remove(taskId);
673         }
674     }
675 
removeWindowInner(View decorView, boolean hideView)676     private void removeWindowInner(View decorView, boolean hideView) {
677         if (mSysuiProxy != null) {
678             mSysuiProxy.requestTopUi(false, TAG);
679         }
680         if (hideView) {
681             decorView.setVisibility(View.GONE);
682         }
683         mWindowManagerGlobal.removeView(decorView, false /* immediate */);
684     }
685 
686     /**
687      * Record the view or surface for a starting window.
688      */
689     private static class StartingWindowRecord {
690         private final IBinder mAppToken;
691         private final View mDecorView;
692         private final TaskSnapshotWindow mTaskSnapshotWindow;
693         private SplashScreenView mContentView;
694         private boolean mSetSplashScreen;
695         @StartingWindowType private int mSuggestType;
696         private int mBGColor;
697         private final long mCreateTime;
698         private int mSystemBarAppearance;
699         private boolean mDrawsSystemBarBackgrounds;
700 
StartingWindowRecord(IBinder appToken, View decorView, TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType)701         StartingWindowRecord(IBinder appToken, View decorView,
702                 TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
703             mAppToken = appToken;
704             mDecorView = decorView;
705             mTaskSnapshotWindow = taskSnapshotWindow;
706             if (mTaskSnapshotWindow != null) {
707                 mBGColor = mTaskSnapshotWindow.getBackgroundColor();
708             }
709             mSuggestType = suggestType;
710             mCreateTime = SystemClock.uptimeMillis();
711         }
712 
setSplashScreenView(SplashScreenView splashScreenView)713         private void setSplashScreenView(SplashScreenView splashScreenView) {
714             if (mSetSplashScreen) {
715                 return;
716             }
717             mContentView = splashScreenView;
718             mSetSplashScreen = true;
719         }
720 
parseAppSystemBarColor(Context context)721         private void parseAppSystemBarColor(Context context) {
722             final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
723             mDrawsSystemBarBackgrounds = a.getBoolean(
724                     R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
725             if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
726                 mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
727             }
728             if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
729                 mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
730             }
731             a.recycle();
732         }
733 
734         // Reset the system bar color which set by splash screen, make it align to the app.
clearSystemBarColor()735         private void clearSystemBarColor() {
736             if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
737                 return;
738             }
739             if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
740                 final WindowManager.LayoutParams lp =
741                         (WindowManager.LayoutParams) mDecorView.getLayoutParams();
742                 if (mDrawsSystemBarBackgrounds) {
743                     lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
744                 } else {
745                     lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
746                 }
747                 mDecorView.setLayoutParams(lp);
748             }
749             mDecorView.getWindowInsetsController().setSystemBarsAppearance(
750                     mSystemBarAppearance, LIGHT_BARS_MASK);
751         }
752     }
753 }
754