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