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