• 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.content.res.Configuration.UI_MODE_NIGHT_MASK;
21 import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
23 import static android.view.Display.DEFAULT_DISPLAY;
24 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
25 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
26 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
27 import static android.window.StartingWindowInfo.TYPE_PARAMETER_APP_PREFERS_ICON;
28 
29 import android.annotation.ColorInt;
30 import android.annotation.IntDef;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.app.ActivityManager;
34 import android.app.ActivityThread;
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.PackageManager;
41 import android.content.res.Configuration;
42 import android.content.res.Resources;
43 import android.content.res.TypedArray;
44 import android.graphics.Bitmap;
45 import android.graphics.Canvas;
46 import android.graphics.Color;
47 import android.graphics.Rect;
48 import android.graphics.drawable.AdaptiveIconDrawable;
49 import android.graphics.drawable.BitmapDrawable;
50 import android.graphics.drawable.ColorDrawable;
51 import android.graphics.drawable.Drawable;
52 import android.graphics.drawable.LayerDrawable;
53 import android.hardware.display.DisplayManager;
54 import android.net.Uri;
55 import android.os.Handler;
56 import android.os.HandlerThread;
57 import android.os.IBinder;
58 import android.os.SystemClock;
59 import android.os.Trace;
60 import android.os.UserHandle;
61 import android.util.ArrayMap;
62 import android.util.DisplayMetrics;
63 import android.util.Slog;
64 import android.view.ContextThemeWrapper;
65 import android.view.Display;
66 import android.view.SurfaceControl;
67 import android.view.WindowManager;
68 import android.window.SplashScreenView;
69 import android.window.StartingWindowInfo;
70 import android.window.StartingWindowInfo.StartingWindowType;
71 
72 import com.android.internal.R;
73 import com.android.internal.annotations.VisibleForTesting;
74 import com.android.internal.graphics.palette.Palette;
75 import com.android.internal.graphics.palette.Quantizer;
76 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
77 import com.android.internal.policy.PhoneWindow;
78 import com.android.internal.protolog.ProtoLog;
79 import com.android.launcher3.icons.BaseIconFactory;
80 import com.android.launcher3.icons.IconProvider;
81 import com.android.wm.shell.protolog.ShellProtoLogGroup;
82 import com.android.wm.shell.shared.TransactionPool;
83 
84 import java.util.List;
85 import java.util.function.Consumer;
86 import java.util.function.IntPredicate;
87 import java.util.function.IntSupplier;
88 import java.util.function.Supplier;
89 import java.util.function.UnaryOperator;
90 
91 /**
92  * Util class to create the view for a splash screen content.
93  * Everything execute in this class should be post to mSplashscreenWorkerHandler.
94  * @hide
95  */
96 public class SplashscreenContentDrawer {
97     private static final String TAG = StartingWindowController.TAG;
98 
99     /**
100      * The minimum duration during which the splash screen is shown when the splash screen icon is
101      * animated.
102      */
103     static final long MINIMAL_ANIMATION_DURATION = 400L;
104 
105     /**
106      * Allow the icon style splash screen to be displayed for longer to give time for the animation
107      * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
108      * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
109      */
110     static final long TIME_WINDOW_DURATION = 100L;
111 
112     /**
113      * The maximum duration during which the splash screen will be shown if the application is ready
114      * to show before the icon animation finishes.
115      */
116     static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
117 
118     private final Context mContext;
119     private final HighResIconProvider mHighResIconProvider;
120     private int mIconSize;
121     private int mDefaultIconSize;
122     private int mBrandingImageWidth;
123     private int mBrandingImageHeight;
124     private int mMainWindowShiftLength;
125     private float mEnlargeForegroundIconThreshold;
126     private float mNoBackgroundScale;
127     private int mLastPackageContextConfigHash;
128     private final TransactionPool mTransactionPool;
129     private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
130     private final Handler mSplashscreenWorkerHandler;
131     private final boolean mCanUseAppIconForSplashScreen;
132     @VisibleForTesting
133     final ColorCache mColorCache;
134 
SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool)135     SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
136         mContext = context;
137         mHighResIconProvider = new HighResIconProvider(mContext, iconProvider);
138         mTransactionPool = pool;
139 
140         // Initialize Splashscreen worker thread
141         // TODO(b/185288910) move it into WMShellConcurrencyModule and provide an executor to make
142         //  it easier to test stuff that happens on that thread later.
143         final HandlerThread shellSplashscreenWorkerThread =
144                 new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
145         shellSplashscreenWorkerThread.start();
146         mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
147         mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
148         mCanUseAppIconForSplashScreen = context.getResources().getBoolean(
149                 com.android.wm.shell.R.bool.config_canUseAppIconForSplashScreen);
150     }
151 
152     /**
153      * Help method to create a layout parameters for a window.
154      */
createContext(Context initContext, StartingWindowInfo windowInfo, int theme, @StartingWindowInfo.StartingWindowType int suggestType, DisplayManager displayManager)155     static Context createContext(Context initContext, StartingWindowInfo windowInfo,
156             int theme, @StartingWindowInfo.StartingWindowType int suggestType,
157             DisplayManager displayManager) {
158         final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
159         final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
160                 ? windowInfo.targetActivityInfo
161                 : taskInfo.topActivityInfo;
162         if (activityInfo == null || activityInfo.packageName == null) {
163             return null;
164         }
165 
166         final int displayId = taskInfo.displayId;
167         final int taskId = taskInfo.taskId;
168 
169         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
170                 "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
171                 activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
172         final Display display = displayManager.getDisplay(displayId);
173         if (display == null) {
174             // Can't show splash screen on requested display, so skip showing at all.
175             return null;
176         }
177         Context context = displayId == DEFAULT_DISPLAY
178                 ? initContext : initContext.createDisplayContext(display);
179         if (context == null) {
180             return null;
181         }
182         if (theme != context.getThemeResId()) {
183             try {
184                 context = context.createPackageContextAsUser(activityInfo.packageName,
185                         CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
186                 context.setTheme(theme);
187             } catch (PackageManager.NameNotFoundException e) {
188                 Slog.w(TAG, "Failed creating package context with package name "
189                         + activityInfo.packageName + " for user " + taskInfo.userId, e);
190                 return null;
191             }
192         }
193 
194         final Configuration taskConfig = taskInfo.getConfiguration();
195         final Configuration contextConfig = context.getResources().getConfiguration();
196         if ((taskConfig.uiMode & UI_MODE_NIGHT_MASK)
197                 != (contextConfig.uiMode & UI_MODE_NIGHT_MASK)) {
198             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
199                     "addSplashScreen: creating context based on task Configuration %s",
200                     taskConfig);
201             final Context overrideContext = context.createConfigurationContext(taskConfig);
202             overrideContext.setTheme(theme);
203             context = overrideContext;
204         }
205         return context;
206     }
207 
208     /**
209      * Creates the window layout parameters for splashscreen window.
210      */
createLayoutParameters(Context context, StartingWindowInfo windowInfo, @StartingWindowInfo.StartingWindowType int suggestType, CharSequence title, int pixelFormat, IBinder appToken)211     static WindowManager.LayoutParams createLayoutParameters(Context context,
212             StartingWindowInfo windowInfo,
213             @StartingWindowInfo.StartingWindowType int suggestType,
214             CharSequence title, int pixelFormat, IBinder appToken) {
215         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
216                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
217         params.setFitInsetsSides(0);
218         params.setFitInsetsTypes(0);
219         params.format = pixelFormat;
220         int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
221                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
222                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
223         final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
224         if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
225             windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
226         }
227         if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
228             if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
229                 windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
230             }
231         } else {
232             windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
233         }
234 
235         final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
236         final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
237                 ? windowInfo.targetActivityInfo
238                 : taskInfo.topActivityInfo;
239         final boolean isEdgeToEdgeEnforced = PhoneWindow.isEdgeToEdgeEnforced(
240                 activityInfo.applicationInfo, false /* local */, a);
241         if (isEdgeToEdgeEnforced) {
242             params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
243         }
244         params.layoutInDisplayCutoutMode = a.getInt(
245                 R.styleable.Window_windowLayoutInDisplayCutoutMode,
246                 isEdgeToEdgeEnforced
247                         ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
248                         : params.layoutInDisplayCutoutMode);
249         params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
250         a.recycle();
251 
252         final int displayId = taskInfo.displayId;
253         // Assumes it's safe to show starting windows of launched apps while
254         // the keyguard is being hidden. This is okay because starting windows never show
255         // secret information.
256         // TODO(b/113840485): Occluded may not only happen on default display
257         if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
258             windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
259         }
260 
261         // Force the window flags: this is a fake window, so it is not really
262         // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
263         // flag because we do know that the next window will take input
264         // focus, so we want to get the IME window up on top of us right away.
265         // Touches will only pass through to the host activity window and will be blocked from
266         // passing to any other windows.
267         windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
268                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
269                 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
270         params.flags = windowFlags;
271         params.token = appToken;
272         params.packageName = activityInfo.packageName;
273         params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
274         params.setTitle("Splash Screen " + title);
275         return params;
276     }
277     /**
278      * Create a SplashScreenView object.
279      *
280      * In order to speed up the splash screen view to show on first frame, preparing the
281      * view on background thread so the view and the drawable can be create and pre-draw in
282      * parallel.
283      *
284      * @param suggestType Suggest type to create the splash screen view.
285      * @param splashScreenViewConsumer Receiving the SplashScreenView object, which will also be
286      *                                 executed on splash screen thread. Note that the view can be
287      *                                 null if failed.
288      */
createContentView(Context context, @StartingWindowType int suggestType, StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer, Consumer<Runnable> uiThreadInitConsumer)289     void createContentView(Context context, @StartingWindowType int suggestType,
290             StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer,
291             Consumer<Runnable> uiThreadInitConsumer) {
292         mSplashscreenWorkerHandler.post(() -> {
293             SplashScreenView contentView;
294             try {
295                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
296                 contentView = makeSplashScreenContentView(context, info, suggestType,
297                         uiThreadInitConsumer);
298                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
299             } catch (RuntimeException e) {
300                 Slog.w(TAG, "failed creating starting window content at taskId: "
301                         + info.taskInfo.taskId, e);
302                 contentView = null;
303             }
304             splashScreenViewConsumer.accept(contentView);
305         });
306     }
307 
updateDensity()308     private void updateDensity() {
309         mIconSize = mContext.getResources().getDimensionPixelSize(
310                 com.android.internal.R.dimen.starting_surface_icon_size);
311         mDefaultIconSize = mContext.getResources().getDimensionPixelSize(
312                 com.android.internal.R.dimen.starting_surface_default_icon_size);
313         mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
314                 com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
315         mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
316                 com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
317         mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
318                 com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
319         mEnlargeForegroundIconThreshold = mContext.getResources().getFloat(
320                 com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold);
321         mNoBackgroundScale = mContext.getResources().getFloat(
322                 com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor);
323     }
324 
325     /**
326      * @return Current system background color.
327      */
getSystemBGColor()328     public static int getSystemBGColor() {
329         final Context systemContext = ActivityThread.currentApplication();
330         if (systemContext == null) {
331             Slog.e(TAG, "System context does not exist!");
332             return Color.BLACK;
333         }
334         final Resources res = systemContext.getResources();
335         return res.getColor(com.android.wm.shell.R.color.splash_window_background_default);
336     }
337 
338     /**
339      * Estimate the background color of the app splash screen, this may take a while so use it only
340      * if there is no starting window exists for that context.
341      **/
estimateTaskBackgroundColor(Context context)342     int estimateTaskBackgroundColor(Context context) {
343         final SplashScreenWindowAttrs windowAttrs = new SplashScreenWindowAttrs();
344         getWindowAttrs(context, windowAttrs);
345         return peekWindowBGColor(context, windowAttrs);
346     }
347 
createDefaultBackgroundDrawable()348     private static Drawable createDefaultBackgroundDrawable() {
349         return new ColorDrawable(getSystemBGColor());
350     }
351 
352     /** Extract the window background color from {@code attrs}. */
peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs)353     private static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
354         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
355         Drawable themeBGDrawable = null;
356         if (attrs.mWindowBgColor != 0) {
357             themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
358         } else if (attrs.mWindowBgResId != 0) {
359             try {
360                 themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
361             } catch (Resources.NotFoundException e) {
362                 Slog.w(TAG, "Unable get drawable from resource", e);
363             }
364         }
365         if (themeBGDrawable == null) {
366             themeBGDrawable = createDefaultBackgroundDrawable();
367             Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
368         }
369         final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable);
370         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
371         return estimatedWindowBGColor;
372     }
373 
estimateWindowBGColor(Drawable themeBGDrawable)374     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
375         final DrawableColorTester themeBGTester = new DrawableColorTester(
376                 themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
377         if (themeBGTester.passFilterRatio() < 0.5f) {
378             // more than half pixels of the window background is translucent, unable to draw
379             Slog.w(TAG, "Window background is translucent, fill background with black color");
380             return getSystemBGColor();
381         } else {
382             return themeBGTester.getDominateColor();
383         }
384     }
385 
peekLegacySplashscreenContent(Context context, SplashScreenWindowAttrs attrs)386     private static Drawable peekLegacySplashscreenContent(Context context,
387             SplashScreenWindowAttrs attrs) {
388         final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
389         final int resId = safeReturnAttrDefault((def) ->
390                 a.getResourceId(R.styleable.Window_windowSplashscreenContent, def), 0);
391         a.recycle();
392         if (resId != 0) {
393             return context.getDrawable(resId);
394         }
395         if (attrs.mWindowBgResId != 0) {
396             return context.getDrawable(attrs.mWindowBgResId);
397         }
398         return null;
399     }
400 
401     /**
402      * Creates a SplashScreenView without read animatable icon and branding image.
403      */
makeSimpleSplashScreenContentView(Context context, StartingWindowInfo info, int themeBGColor)404     SplashScreenView makeSimpleSplashScreenContentView(Context context,
405             StartingWindowInfo info, int themeBGColor) {
406         updateDensity();
407         mTmpAttrs.reset();
408         final ActivityInfo ai = info.targetActivityInfo != null
409                 ? info.targetActivityInfo
410                 : info.taskInfo.topActivityInfo;
411 
412         final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
413         final SplashScreenView view = builder
414                 .setWindowBGColor(themeBGColor)
415                 .chooseStyle(STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN)
416                 .build();
417         view.setNotCopyable();
418         return view;
419     }
420 
makeSplashScreenContentView(Context context, StartingWindowInfo info, @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer)421     private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
422             @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
423         updateDensity();
424 
425         getWindowAttrs(context, mTmpAttrs);
426         mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();
427 
428         final @StartingWindowType int splashType =
429                 suggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN && !canUseIcon(info)
430                 ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN : suggestType;
431         final Drawable legacyDrawable = splashType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
432                 ? peekLegacySplashscreenContent(context, mTmpAttrs) : null;
433         final ActivityInfo ai = info.targetActivityInfo != null
434                 ? info.targetActivityInfo
435                 : info.taskInfo.topActivityInfo;
436         final int themeBGColor = legacyDrawable != null
437                 ? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
438                 : getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
439 
440         return new SplashViewBuilder(context, ai)
441                 .setWindowBGColor(themeBGColor)
442                 .overlayDrawable(legacyDrawable)
443                 .chooseStyle(splashType)
444                 .setUiThreadInitConsumer(uiThreadInitConsumer)
445                 .setAllowHandleSolidColor(info.allowHandleSolidColorSplashScreen())
446                 .build();
447     }
448 
canUseIcon(StartingWindowInfo info)449     private boolean canUseIcon(StartingWindowInfo info) {
450         return mCanUseAppIconForSplashScreen || mTmpAttrs.mSplashScreenIcon != null
451              || (info.startingWindowTypeParameter & TYPE_PARAMETER_APP_PREFERS_ICON) != 0;
452     }
453 
getBGColorFromCache(ActivityInfo ai, IntSupplier windowBgColorSupplier)454     private int getBGColorFromCache(ActivityInfo ai, IntSupplier windowBgColorSupplier) {
455         return mColorCache.getWindowColor(ai.packageName, mLastPackageContextConfigHash,
456                 mTmpAttrs.mWindowBgColor, mTmpAttrs.mWindowBgResId, windowBgColorSupplier).mBgColor;
457     }
458 
safeReturnAttrDefault(UnaryOperator<T> getMethod, T def)459     private static <T> T safeReturnAttrDefault(UnaryOperator<T> getMethod, T def) {
460         try {
461             return getMethod.apply(def);
462         } catch (RuntimeException e) {
463             Slog.w(TAG, "Get attribute fail, return default: " + e.getMessage());
464             return def;
465         }
466     }
467 
468     /**
469      * Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into
470      * {@code attrs}.
471      */
getWindowAttrs(Context context, SplashScreenWindowAttrs attrs)472     private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
473         final TypedArray typedArray = context.obtainStyledAttributes(
474                 com.android.internal.R.styleable.Window);
475         attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
476         attrs.mWindowBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
477                 R.styleable.Window_windowSplashScreenBackground, def),
478                 Color.TRANSPARENT);
479         attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
480                 R.styleable.Window_windowSplashScreenAnimatedIcon), null);
481         attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
482                 R.styleable.Window_windowSplashScreenBrandingImage), null);
483         attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
484                 R.styleable.Window_windowSplashScreenIconBackgroundColor, def),
485                 Color.TRANSPARENT);
486         typedArray.recycle();
487         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
488                 "getWindowAttrs: window attributes color: %s, replace icon: %b",
489                 Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null);
490     }
491 
492     /** Creates the wrapper with system theme to avoid unexpected styles from app. */
createViewContextWrapper(Context appContext)493     ContextThemeWrapper createViewContextWrapper(Context appContext) {
494         return new ContextThemeWrapper(appContext, mContext.getTheme());
495     }
496 
497     /** The configuration of the splash screen window. */
498     public static class SplashScreenWindowAttrs {
499         private int mWindowBgResId = 0;
500         private int mWindowBgColor = Color.TRANSPARENT;
501         private Drawable mSplashScreenIcon = null;
502         private Drawable mBrandingImage = null;
503         private int mIconBgColor = Color.TRANSPARENT;
504 
reset()505         void reset() {
506             mWindowBgResId = 0;
507             mWindowBgColor = Color.TRANSPARENT;
508             mSplashScreenIcon = null;
509             mBrandingImage = null;
510             mIconBgColor = Color.TRANSPARENT;
511         }
512     }
513 
514     /**
515      * Get an optimal animation duration to keep the splash screen from showing.
516      *
517      * @param animationDuration The animation duration defined from app.
518      * @param appReadyDuration The real duration from the starting the app to the first app window
519      *                         drawn.
520      */
521     @VisibleForTesting
getShowingDuration(long animationDuration, long appReadyDuration)522     static long getShowingDuration(long animationDuration, long appReadyDuration) {
523         if (animationDuration <= appReadyDuration) {
524             // app window ready took longer time than animation, it can be removed ASAP.
525             return appReadyDuration;
526         }
527         if (appReadyDuration < MAX_ANIMATION_DURATION) {
528             if (animationDuration > MAX_ANIMATION_DURATION
529                     || appReadyDuration < MINIMAL_ANIMATION_DURATION) {
530                 // animation is too long or too short, cut off with minimal duration
531                 return MINIMAL_ANIMATION_DURATION;
532             }
533             // animation is longer than dOpt but shorter than max, allow it to play till finish
534             return MAX_ANIMATION_DURATION;
535         }
536         // the shortest duration is longer than dMax, cut off no matter how long the animation
537         // will be.
538         return appReadyDuration;
539     }
540 
541     private class SplashViewBuilder {
542         private final Context mContext;
543         private final ActivityInfo mActivityInfo;
544 
545         private Drawable mOverlayDrawable;
546         private int mSuggestType;
547         private int mThemeColor;
548         private Drawable[] mFinalIconDrawables;
549         private int mFinalIconSize = mIconSize;
550         private Consumer<Runnable> mUiThreadInitTask;
551         /** @see #setAllowHandleSolidColor(boolean) **/
552         private boolean mAllowHandleSolidColor;
553 
SplashViewBuilder(@onNull Context context, @NonNull ActivityInfo aInfo)554         SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
555             mContext = context;
556             mActivityInfo = aInfo;
557         }
558 
setWindowBGColor(@olorInt int background)559         SplashViewBuilder setWindowBGColor(@ColorInt int background) {
560             mThemeColor = background;
561             return this;
562         }
563 
overlayDrawable(Drawable overlay)564         SplashViewBuilder overlayDrawable(Drawable overlay) {
565             mOverlayDrawable = overlay;
566             return this;
567         }
568 
chooseStyle(int suggestType)569         SplashViewBuilder chooseStyle(int suggestType) {
570             mSuggestType = suggestType;
571             return this;
572         }
573 
574         // Set up the UI thread for the View.
setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask)575         SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
576             mUiThreadInitTask = uiThreadInitTask;
577             return this;
578         }
579 
580         /**
581          * If true, the application will receive a the
582          * {@link
583          * android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
584          * callback, effectively copying the {@link SplashScreenView} into the client process.
585          */
setAllowHandleSolidColor(boolean allowHandleSolidColor)586         SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
587             mAllowHandleSolidColor = allowHandleSolidColor;
588             return this;
589         }
590 
build()591         SplashScreenView build() {
592             Drawable iconDrawable;
593             if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
594                     || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
595                 // empty or legacy splash screen case
596                 mFinalIconSize = 0;
597             } else if (mTmpAttrs.mSplashScreenIcon != null) {
598                 // Using the windowSplashScreenAnimatedIcon attribute
599                 iconDrawable = mTmpAttrs.mSplashScreenIcon;
600 
601                 // There is no background below the icon, so scale the icon up
602                 if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
603                         || mTmpAttrs.mIconBgColor == mThemeColor) {
604                     mFinalIconSize *= mNoBackgroundScale;
605                 }
606                 createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
607             } else {
608                 final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
609                 final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
610                 final int scaledIconDpi =
611                         (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale);
612                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
613                 iconDrawable = mHighResIconProvider.getIcon(
614                         mActivityInfo, densityDpi, scaledIconDpi);
615                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
616                 if (!processAdaptiveIcon(iconDrawable)) {
617                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
618                             "The icon is not an AdaptiveIconDrawable");
619                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "legacy_icon_factory");
620                     final ShapeIconFactory factory = new ShapeIconFactory(
621                             SplashscreenContentDrawer.this.mContext,
622                             scaledIconDpi, mFinalIconSize);
623                     final Bitmap bitmap = factory.createScaledBitmap(iconDrawable,
624                             BaseIconFactory.MODE_DEFAULT);
625                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
626                     createIconDrawable(new BitmapDrawable(bitmap), true,
627                             mHighResIconProvider.mLoadInDetail);
628                 }
629             }
630 
631             return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask);
632         }
633 
634         private class ShapeIconFactory extends BaseIconFactory {
ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize)635             protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
636                 super(context, fillResIconDpi, iconBitmapSize);
637             }
638         }
639 
createIconDrawable(Drawable iconDrawable, boolean legacy, boolean loadInDetail)640         private void createIconDrawable(Drawable iconDrawable, boolean legacy,
641                 boolean loadInDetail) {
642             if (legacy) {
643                 mFinalIconDrawables = SplashscreenIconDrawableFactory.makeLegacyIconDrawable(
644                         iconDrawable, mDefaultIconSize, mFinalIconSize, loadInDetail,
645                         mSplashscreenWorkerHandler);
646             } else {
647                 mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
648                         mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
649                         mFinalIconSize, loadInDetail, mSplashscreenWorkerHandler);
650             }
651         }
652 
processAdaptiveIcon(Drawable iconDrawable)653         private boolean processAdaptiveIcon(Drawable iconDrawable) {
654             if (!(iconDrawable instanceof AdaptiveIconDrawable)) {
655                 return false;
656             }
657 
658             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
659             final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) iconDrawable;
660             final Drawable iconForeground = adaptiveIconDrawable.getForeground();
661             final ColorCache.IconColor iconColor = mColorCache.getIconColor(
662                     mActivityInfo.packageName, mActivityInfo.getIconResource(),
663                     mLastPackageContextConfigHash,
664                     () -> new DrawableColorTester(iconForeground,
665                             DrawableColorTester.TRANSLUCENT_FILTER /* filterType */),
666                     () -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
667             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
668                     "processAdaptiveIcon: FgMainColor=%s, BgMainColor=%s, "
669                             + "IsBgComplex=%b, FromCache=%b, ThemeColor=%s",
670                     Integer.toHexString(iconColor.mFgColor),
671                     Integer.toHexString(iconColor.mBgColor),
672                     iconColor.mIsBgComplex,
673                     iconColor.mReuseCount > 0,
674                     Integer.toHexString(mThemeColor));
675 
676             // Only draw the foreground of AdaptiveIcon to the splash screen if below condition
677             // meet:
678             // A. The background of the adaptive icon is not complicated. If it is complicated,
679             // it may contain some information, and
680             // B. The background of the adaptive icon is similar to the theme color, or
681             // C. The background of the adaptive icon is grayscale, and the foreground of the
682             // adaptive icon forms a certain contrast with the theme color.
683             // D. Didn't specify icon background color.
684             if (iconForeground != null
685                     && !iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
686                     && (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
687                             || (iconColor.mIsBgGrayscale
688                                     && !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
689                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
690                         "processAdaptiveIcon: choose fg icon");
691                 // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
692                 // scale by 192/160 if we only draw adaptiveIcon's foreground.
693                 final float noBgScale =
694                         iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold
695                                 ? mNoBackgroundScale : 1f;
696                 // Using AdaptiveIconDrawable here can help keep the shape consistent with the
697                 // current settings.
698                 mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
699                 createIconDrawable(iconForeground, false, mHighResIconProvider.mLoadInDetail);
700             } else {
701                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
702                         "processAdaptiveIcon: draw whole icon");
703                 createIconDrawable(iconDrawable, false, mHighResIconProvider.mLoadInDetail);
704             }
705             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
706             return true;
707         }
708 
709         private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
710                 Consumer<Runnable> uiThreadInitTask) {
711             Drawable foreground = null;
712             Drawable background = null;
713             if (iconDrawable != null) {
714                 foreground = iconDrawable.length > 0 ? iconDrawable[0] : null;
715                 background = iconDrawable.length > 1 ? iconDrawable[1] : null;
716             }
717 
718             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
719             final ContextThemeWrapper wrapper = createViewContextWrapper(mContext);
720             final SplashScreenView.Builder builder = new SplashScreenView.Builder(wrapper)
721                     .setBackgroundColor(mThemeColor)
722                     .setOverlayDrawable(mOverlayDrawable)
723                     .setIconSize(iconSize)
724                     .setIconBackground(background)
725                     .setCenterViewDrawable(foreground)
726                     .setUiThreadInitConsumer(uiThreadInitTask)
727                     .setAllowHandleSolidColor(mAllowHandleSolidColor);
728 
729             if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
730                     && mTmpAttrs.mBrandingImage != null) {
731                 builder.setBrandingDrawable(mTmpAttrs.mBrandingImage, mBrandingImageWidth,
732                         mBrandingImageHeight);
733             }
734             final SplashScreenView splashScreenView = builder.build();
735             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
736             return splashScreenView;
737         }
738     }
739 
isRgbSimilarInHsv(int a, int b)740     private static boolean isRgbSimilarInHsv(int a, int b) {
741         if (a == b) {
742             return true;
743         }
744         final float lumA = Color.luminance(a);
745         final float lumB = Color.luminance(b);
746         final float contrastRatio = lumA > lumB
747                 ? (lumA + 0.05f) / (lumB + 0.05f) : (lumB + 0.05f) / (lumA + 0.05f);
748         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
749                 "isRgbSimilarInHsv a:%s, b:%s, contrast ratio:%f",
750                 Integer.toHexString(a), Integer.toHexString(b), contrastRatio);
751         if (contrastRatio < 2) {
752             return true;
753         }
754 
755         final float[] aHsv = new float[3];
756         final float[] bHsv = new float[3];
757         Color.colorToHSV(a, aHsv);
758         Color.colorToHSV(b, bHsv);
759         // Minimum degree of the hue between two colors, the result range is 0-180.
760         int minAngle = (int) Math.abs(aHsv[0] - bHsv[0]);
761         minAngle = (minAngle + 180) % 360 - 180;
762 
763         // Calculate the difference between two colors based on the HSV dimensions.
764         final float normalizeH = minAngle / 180f;
765         final double squareH = Math.pow(normalizeH, 2);
766         final double squareS = Math.pow(aHsv[1] - bHsv[1], 2);
767         final double squareV = Math.pow(aHsv[2] - bHsv[2], 2);
768         final double square = squareH + squareS + squareV;
769         final double mean = square / 3;
770         final double root = Math.sqrt(mean);
771         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
772                 "isRgbSimilarInHsv hsvDiff: %d, ah: %f, bh: %f, as: %f, bs: %f, av: %f, bv: %f, "
773                         + "sqH: %f, sqS: %f, sqV: %f, rsm: %f",
774                 minAngle, aHsv[0], bHsv[0], aHsv[1], bHsv[1], aHsv[2], bHsv[2],
775                 squareH, squareS, squareV, root);
776         return root < 0.1;
777     }
778 
779     private static class DrawableColorTester {
780         private static final int NO_ALPHA_FILTER = 0;
781         // filter out completely invisible pixels
782         private static final int TRANSPARENT_FILTER = 1;
783         // filter out translucent and invisible pixels
784         private static final int TRANSLUCENT_FILTER = 2;
785 
786         @IntDef(flag = true, value = {
787                 NO_ALPHA_FILTER,
788                 TRANSPARENT_FILTER,
789                 TRANSLUCENT_FILTER
790         })
791         private @interface QuantizerFilterType {}
792 
793         private final ColorTester mColorChecker;
794 
DrawableColorTester(Drawable drawable)795         DrawableColorTester(Drawable drawable) {
796             this(drawable, NO_ALPHA_FILTER /* filterType */);
797         }
798 
DrawableColorTester(Drawable drawable, @QuantizerFilterType int filterType)799         DrawableColorTester(Drawable drawable, @QuantizerFilterType int filterType) {
800             // Some applications use LayerDrawable for their windowBackground. To ensure that we
801             // only get the real background, so that the color is not affected by the alpha of the
802             // upper layer, try to get the lower layer here. This can also speed up the calculation.
803             if (drawable instanceof LayerDrawable) {
804                 LayerDrawable layerDrawable = (LayerDrawable) drawable;
805                 if (layerDrawable.getNumberOfLayers() > 0) {
806                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
807                             "DrawableColorTester: replace drawable with bottom layer drawable");
808                     drawable = layerDrawable.getDrawable(0);
809                 }
810             }
811             if (drawable == null) {
812                 mColorChecker = new SingleColorTester(
813                         (ColorDrawable) createDefaultBackgroundDrawable());
814             } else {
815                 mColorChecker = drawable instanceof ColorDrawable
816                         ? new SingleColorTester((ColorDrawable) drawable)
817                         : new ComplexDrawableTester(drawable, filterType);
818             }
819         }
820 
passFilterRatio()821         public float passFilterRatio() {
822             return mColorChecker.passFilterRatio();
823         }
824 
isComplexColor()825         public boolean isComplexColor() {
826             return mColorChecker.isComplexColor();
827         }
828 
getDominateColor()829         public int getDominateColor() {
830             return mColorChecker.getDominantColor();
831         }
832 
isGrayscale()833         public boolean isGrayscale() {
834             return mColorChecker.isGrayscale();
835         }
836 
837         /**
838          * A help class to check the color information from a Drawable.
839          */
840         private interface ColorTester {
passFilterRatio()841             float passFilterRatio();
842 
isComplexColor()843             boolean isComplexColor();
844 
getDominantColor()845             int getDominantColor();
846 
isGrayscale()847             boolean isGrayscale();
848         }
849 
isGrayscaleColor(int color)850         private static boolean isGrayscaleColor(int color) {
851             final int red = Color.red(color);
852             final int green = Color.green(color);
853             final int blue = Color.blue(color);
854             return red == green && green == blue;
855         }
856 
857         /**
858          * For ColorDrawable only. There will be only one color so don't spend too much resource for
859          * it.
860          */
861         private static class SingleColorTester implements ColorTester {
862             private final ColorDrawable mColorDrawable;
863 
SingleColorTester(@onNull ColorDrawable drawable)864             SingleColorTester(@NonNull ColorDrawable drawable) {
865                 mColorDrawable = drawable;
866             }
867 
868             @Override
passFilterRatio()869             public float passFilterRatio() {
870                 final int alpha = mColorDrawable.getAlpha();
871                 return alpha / 255.0f;
872             }
873 
874             @Override
isComplexColor()875             public boolean isComplexColor() {
876                 return false;
877             }
878 
879             @Override
getDominantColor()880             public int getDominantColor() {
881                 return mColorDrawable.getColor();
882             }
883 
884             @Override
isGrayscale()885             public boolean isGrayscale() {
886                 return isGrayscaleColor(mColorDrawable.getColor());
887             }
888         }
889 
890         /**
891          * For any other Drawable except ColorDrawable. This will use the Palette API to check the
892          * color information and use a quantizer to filter out transparent colors when needed.
893          */
894         private static class ComplexDrawableTester implements ColorTester {
895             private static final int MAX_BITMAP_SIZE = 40;
896             private final Palette mPalette;
897             private final boolean mFilterTransparent;
898             private static final AlphaFilterQuantizer ALPHA_FILTER_QUANTIZER =
899                     new AlphaFilterQuantizer();
900 
901             /**
902              * @param drawable The test target.
903              * @param filterType Targeting to filter out transparent or translucent pixels,
904              *                   this would be needed if want to check
905              *                   {@link #passFilterRatio()}, also affecting the estimated result
906              *                   of the dominant color.
907              */
ComplexDrawableTester(Drawable drawable, @QuantizerFilterType int filterType)908             ComplexDrawableTester(Drawable drawable, @QuantizerFilterType int filterType) {
909                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
910                 final Rect initialBounds = drawable.copyBounds();
911                 int width = drawable.getIntrinsicWidth();
912                 int height = drawable.getIntrinsicHeight();
913                 // Some drawables do not have intrinsic dimensions
914                 if (width <= 0 || height <= 0) {
915                     width = MAX_BITMAP_SIZE;
916                     height = MAX_BITMAP_SIZE;
917                 } else {
918                     width = Math.min(width, MAX_BITMAP_SIZE);
919                     height = Math.min(height, MAX_BITMAP_SIZE);
920                 }
921 
922                 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
923                 final Canvas bmpCanvas = new Canvas(bitmap);
924                 drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
925                 drawable.draw(bmpCanvas);
926                 // restore to original bounds
927                 drawable.setBounds(initialBounds);
928 
929                 final Palette.Builder builder;
930                 // The Palette API will ignore Alpha, so it cannot handle transparent pixels, but
931                 // sometimes we will need this information to know if this Drawable object is
932                 // transparent.
933                 mFilterTransparent = filterType != NO_ALPHA_FILTER;
934                 if (mFilterTransparent) {
935                     ALPHA_FILTER_QUANTIZER.setFilter(filterType);
936                     builder = new Palette.Builder(bitmap, ALPHA_FILTER_QUANTIZER)
937                             .maximumColorCount(5);
938                 } else {
939                     builder = new Palette.Builder(bitmap, null)
940                             .maximumColorCount(5);
941                 }
942                 mPalette = builder.generate();
943                 bitmap.recycle();
944                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
945             }
946 
947             @Override
passFilterRatio()948             public float passFilterRatio() {
949                 return mFilterTransparent ? ALPHA_FILTER_QUANTIZER.mPassFilterRatio : 1;
950             }
951 
952             @Override
isComplexColor()953             public boolean isComplexColor() {
954                 return mPalette.getSwatches().size() > 1;
955             }
956 
957             @Override
getDominantColor()958             public int getDominantColor() {
959                 final Palette.Swatch mainSwatch = mPalette.getDominantSwatch();
960                 if (mainSwatch != null) {
961                     return mainSwatch.getInt();
962                 }
963                 return Color.BLACK;
964             }
965 
966             @Override
isGrayscale()967             public boolean isGrayscale() {
968                 final List<Palette.Swatch> swatches = mPalette.getSwatches();
969                 if (swatches != null) {
970                     for (int i = swatches.size() - 1; i >= 0; i--) {
971                         Palette.Swatch swatch = swatches.get(i);
972                         if (!isGrayscaleColor(swatch.getInt())) {
973                             return false;
974                         }
975                     }
976                 }
977                 return true;
978             }
979 
980             private static class AlphaFilterQuantizer implements Quantizer {
981                 private static final int NON_TRANSPARENT = 0xFF000000;
982                 private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer();
983                 private final IntPredicate mTransparentFilter = i -> (i & NON_TRANSPARENT) != 0;
984                 private final IntPredicate mTranslucentFilter = i ->
985                         (i & NON_TRANSPARENT) == NON_TRANSPARENT;
986 
987                 private IntPredicate mFilter = mTransparentFilter;
988                 private float mPassFilterRatio;
989 
setFilter(@uantizerFilterType int filterType)990                 void setFilter(@QuantizerFilterType int filterType) {
991                     switch (filterType) {
992                         case TRANSLUCENT_FILTER:
993                             mFilter = mTranslucentFilter;
994                             break;
995                         case TRANSPARENT_FILTER:
996                         default:
997                             mFilter = mTransparentFilter;
998                             break;
999                     }
1000                 }
1001 
1002                 @Override
quantize(final int[] pixels, final int maxColors)1003                 public void quantize(final int[] pixels, final int maxColors) {
1004                     mPassFilterRatio = 0;
1005                     int realSize = 0;
1006                     for (int i = pixels.length - 1; i > 0; i--) {
1007                         if (mFilter.test(pixels[i])) {
1008                             realSize++;
1009                         }
1010                     }
1011                     if (realSize == 0) {
1012                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
1013                                 "DrawableTester quantize: pure transparent image");
1014                         mInnerQuantizer.quantize(pixels, maxColors);
1015                         return;
1016                     }
1017                     mPassFilterRatio = (float) realSize / pixels.length;
1018                     final int[] samplePixels = new int[realSize];
1019                     int rowIndex = 0;
1020                     for (int i = pixels.length - 1; i > 0; i--) {
1021                         if (mFilter.test(pixels[i])) {
1022                             samplePixels[rowIndex] = pixels[i];
1023                             rowIndex++;
1024                         }
1025                     }
1026                     mInnerQuantizer.quantize(samplePixels, maxColors);
1027                 }
1028 
1029                 @Override
getQuantizedColors()1030                 public List<Palette.Swatch> getQuantizedColors() {
1031                     return mInnerQuantizer.getQuantizedColors();
1032                 }
1033             }
1034         }
1035     }
1036 
1037     /** Cache the result of {@link DrawableColorTester} to reduce expensive calculation. */
1038     @VisibleForTesting
1039     static class ColorCache extends BroadcastReceiver {
1040         /**
1041          * The color may be different according to resource id and configuration (e.g. night mode),
1042          * so this allows to cache more than one color per package.
1043          */
1044         private static final int CACHE_SIZE = 2;
1045 
1046         /** The computed colors of packages. */
1047         private final ArrayMap<String, Colors> mColorMap = new ArrayMap<>();
1048 
1049         private static class Colors {
1050             final WindowColor[] mWindowColors = new WindowColor[CACHE_SIZE];
1051             final IconColor[] mIconColors = new IconColor[CACHE_SIZE];
1052         }
1053 
1054         private static class Cache {
1055             /** The hash used to check whether this cache is hit. */
1056             final int mHash;
1057 
1058             /** The number of times this cache has been reused. */
1059             int mReuseCount;
1060 
Cache(int hash)1061             Cache(int hash) {
1062                 mHash = hash;
1063             }
1064         }
1065 
1066         static class WindowColor extends Cache {
1067             final int mBgColor;
1068 
WindowColor(int hash, int bgColor)1069             WindowColor(int hash, int bgColor) {
1070                 super(hash);
1071                 mBgColor = bgColor;
1072             }
1073         }
1074 
1075         static class IconColor extends Cache {
1076             final int mFgColor;
1077             final int mBgColor;
1078             final boolean mIsBgComplex;
1079             final boolean mIsBgGrayscale;
1080             final float mFgNonTranslucentRatio;
1081 
IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex, boolean isBgGrayscale, float fgNonTranslucnetRatio)1082             IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex,
1083                     boolean isBgGrayscale, float fgNonTranslucnetRatio) {
1084                 super(hash);
1085                 mFgColor = fgColor;
1086                 mBgColor = bgColor;
1087                 mIsBgComplex = isBgComplex;
1088                 mIsBgGrayscale = isBgGrayscale;
1089                 mFgNonTranslucentRatio = fgNonTranslucnetRatio;
1090             }
1091         }
1092 
ColorCache(Context context, Handler handler)1093         ColorCache(Context context, Handler handler) {
1094             // This includes reinstall and uninstall.
1095             final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
1096             filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
1097             context.registerReceiverAsUser(this, UserHandle.ALL, filter,
1098                     null /* broadcastPermission */, handler);
1099         }
1100 
1101         @Override
onReceive(Context context, Intent intent)1102         public void onReceive(Context context, Intent intent) {
1103             final Uri packageUri = intent.getData();
1104             if (packageUri != null) {
1105                 mColorMap.remove(packageUri.getEncodedSchemeSpecificPart());
1106             }
1107         }
1108 
1109         /**
1110          * Gets the existing cache if the hash matches. If null is returned, the caller can use
1111          * outLeastUsedIndex to put the new cache.
1112          */
getCache(T[] caches, int hash, int[] outLeastUsedIndex)1113         private static <T extends Cache> T getCache(T[] caches, int hash, int[] outLeastUsedIndex) {
1114             int minReuseCount = Integer.MAX_VALUE;
1115             for (int i = 0; i < CACHE_SIZE; i++) {
1116                 final T cache = caches[i];
1117                 if (cache == null) {
1118                     // Empty slot has the highest priority to put new cache.
1119                     minReuseCount = -1;
1120                     outLeastUsedIndex[0] = i;
1121                     continue;
1122                 }
1123                 if (cache.mHash == hash) {
1124                     cache.mReuseCount++;
1125                     return cache;
1126                 }
1127                 if (cache.mReuseCount < minReuseCount) {
1128                     minReuseCount = cache.mReuseCount;
1129                     outLeastUsedIndex[0] = i;
1130                 }
1131             }
1132             return null;
1133         }
1134 
getWindowColor(String packageName, int configHash, int windowBgColor, int windowBgResId, IntSupplier windowBgColorSupplier)1135         @NonNull WindowColor getWindowColor(String packageName, int configHash, int windowBgColor,
1136                 int windowBgResId, IntSupplier windowBgColorSupplier) {
1137             Colors colors = mColorMap.get(packageName);
1138             int hash = 31 * configHash + windowBgColor;
1139             hash = 31 * hash + windowBgResId;
1140             final int[] leastUsedIndex = { 0 };
1141             if (colors != null) {
1142                 final WindowColor windowColor = getCache(colors.mWindowColors, hash,
1143                         leastUsedIndex);
1144                 if (windowColor != null) {
1145                     return windowColor;
1146                 }
1147             } else {
1148                 colors = new Colors();
1149                 mColorMap.put(packageName, colors);
1150             }
1151             final WindowColor windowColor = new WindowColor(hash, windowBgColorSupplier.getAsInt());
1152             colors.mWindowColors[leastUsedIndex[0]] = windowColor;
1153             return windowColor;
1154         }
1155 
getIconColor(String packageName, int configHash, int iconResId, Supplier<DrawableColorTester> fgColorTesterSupplier, Supplier<DrawableColorTester> bgColorTesterSupplier)1156         @NonNull IconColor getIconColor(String packageName, int configHash, int iconResId,
1157                 Supplier<DrawableColorTester> fgColorTesterSupplier,
1158                 Supplier<DrawableColorTester> bgColorTesterSupplier) {
1159             Colors colors = mColorMap.get(packageName);
1160             final int hash = configHash * 31 + iconResId;
1161             final int[] leastUsedIndex = { 0 };
1162             if (colors != null) {
1163                 final IconColor iconColor = getCache(colors.mIconColors, hash, leastUsedIndex);
1164                 if (iconColor != null) {
1165                     return iconColor;
1166                 }
1167             } else {
1168                 colors = new Colors();
1169                 mColorMap.put(packageName, colors);
1170             }
1171             final DrawableColorTester fgTester = fgColorTesterSupplier.get();
1172             final DrawableColorTester bgTester = bgColorTesterSupplier.get();
1173             final IconColor iconColor = new IconColor(hash, fgTester.getDominateColor(),
1174                     bgTester.getDominateColor(), bgTester.isComplexColor(), bgTester.isGrayscale(),
1175                     fgTester.passFilterRatio());
1176             colors.mIconColors[leastUsedIndex[0]] = iconColor;
1177             return iconColor;
1178         }
1179     }
1180 
1181     /**
1182      * Create and play the default exit animation for splash screen view.
1183      */
applyExitAnimation(SplashScreenView view, SurfaceControl leash, Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius)1184     void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
1185             Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
1186         final Runnable playAnimation = () -> {
1187             final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
1188                     view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
1189                     roundedCornerRadius);
1190             animation.startAnimations();
1191         };
1192         if (view.getIconView() == null) {
1193             playAnimation.run();
1194             return;
1195         }
1196         final long appReadyDuration = SystemClock.uptimeMillis() - createTime;
1197         final long animDuration = view.getIconAnimationDuration() != null
1198                 ? view.getIconAnimationDuration().toMillis() : 0;
1199         final long minimumShowingDuration = getShowingDuration(animDuration, appReadyDuration);
1200         final long delayed = minimumShowingDuration - appReadyDuration;
1201         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
1202                 "applyExitAnimation delayed: %s", delayed);
1203         if (delayed > 0) {
1204             view.postDelayed(playAnimation, delayed);
1205         } else {
1206             playAnimation.run();
1207         }
1208     }
1209 
1210     /**
1211      * When loading a BitmapDrawable object with specific density, there will decode the image based
1212      * on the density from display metrics, so even when load with higher override density, the
1213      * final intrinsic size of a BitmapDrawable can still not big enough to draw on expect size.
1214      *
1215      * So here we use a standalone IconProvider object to load the Drawable object for higher
1216      * density, and the resources object won't affect the entire system.
1217      *
1218      */
1219     private static class HighResIconProvider {
1220         private final Context mSharedContext;
1221         private final IconProvider mSharedIconProvider;
1222         private boolean mLoadInDetail;
1223 
1224         // only create standalone icon provider when the density dpi is low.
1225         private Context mStandaloneContext;
1226         private IconProvider mStandaloneIconProvider;
1227 
HighResIconProvider(Context context, IconProvider sharedIconProvider)1228         HighResIconProvider(Context context, IconProvider sharedIconProvider) {
1229             mSharedContext = context;
1230             mSharedIconProvider = sharedIconProvider;
1231         }
1232 
getIcon(ActivityInfo activityInfo, int currentDpi, int iconDpi)1233         Drawable getIcon(ActivityInfo activityInfo, int currentDpi, int iconDpi) {
1234             mLoadInDetail = false;
1235             Drawable drawable;
1236             if (currentDpi < iconDpi && currentDpi < DisplayMetrics.DENSITY_XHIGH) {
1237                 drawable = loadFromStandalone(activityInfo, currentDpi, iconDpi);
1238             } else {
1239                 drawable = mSharedIconProvider.getIcon(activityInfo, iconDpi);
1240             }
1241 
1242             if (drawable == null) {
1243                 drawable = mSharedContext.getPackageManager().getDefaultActivityIcon();
1244             }
1245             return drawable;
1246         }
1247 
loadFromStandalone(ActivityInfo activityInfo, int currentDpi, int iconDpi)1248         private Drawable loadFromStandalone(ActivityInfo activityInfo, int currentDpi,
1249                 int iconDpi) {
1250             if (mStandaloneContext == null) {
1251                 final Configuration defConfig = mSharedContext.getResources().getConfiguration();
1252                 mStandaloneContext = mSharedContext.createConfigurationContext(defConfig);
1253                 mStandaloneIconProvider = new IconProvider(mStandaloneContext);
1254             }
1255             Resources resources;
1256             try {
1257                 resources = mStandaloneContext.getPackageManager()
1258                         .getResourcesForApplication(activityInfo.applicationInfo);
1259             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) {
1260                 resources = null;
1261             }
1262             if (resources != null) {
1263                 updateResourcesDpi(resources, iconDpi);
1264             }
1265             final Drawable drawable = mStandaloneIconProvider.getIcon(activityInfo, iconDpi);
1266             mLoadInDetail = true;
1267             // reset density dpi
1268             if (resources != null) {
1269                 updateResourcesDpi(resources, currentDpi);
1270             }
1271             return drawable;
1272         }
1273 
updateResourcesDpi(Resources resources, int densityDpi)1274         private void updateResourcesDpi(Resources resources, int densityDpi) {
1275             final Configuration config = resources.getConfiguration();
1276             final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
1277             config.densityDpi = densityDpi;
1278             displayMetrics.densityDpi = densityDpi;
1279             resources.updateConfiguration(config, displayMetrics);
1280         }
1281     }
1282 }
1283