From edb7c22c0d8a58b459d3b6f329c5ed1bfa930402 Mon Sep 17 00:00:00 2001 From: James O'Leary Date: Mon, 12 Jul 2021 10:10:19 -0400 Subject: [PATCH] Fix WallpaperPicker not being able to assign colors - ThemeOverlayController intentionally chooses transparent as the theme color if there aren't any wallpaper colors (ex. a Live Wallpaper that doesn't send WallpaperColors objects to WallpaperManagerService) - Rather than refactor old code, we patched by ensuring any attempts to use less-than-opaque colors for a theme are replaced by using a default color (ag/15193364) - However, WallpaperPicker sends transparent colors when setting a new theme color - WallpaperPicker's ColorUtils.toColorString turns a color into a 6 characters hex code when transmitting it to framework via JSON stored in a setting. - Six characters can only contain red, green, and blue channels, not alpha. - When the six character string is parsed by Integer.parseInt(), the resulting color has 0 alpha, and the new theme color is rejected in favor of the default color - Using Color.parseColor ensures that alpha is correctly set, whether callers use a 6 or 8 character hex code, and ensuring the input to Color.parseColor starts with a # sign ensures it will work properly Test: Add logs to WallpaperPicker, verifying points at which it manipulates hex codes, ensuring WallpaperPicker is receiving colors with a full alpha channel and merely losing it upon converting it to a 6 digit string. Make this patch to framework. Check implementation of Color.parseColor, ensure we meet its requirements for arguments by sanitizing inputs from callers (i.e. add a # sign in front). Uninstall patches wallpaper picker, reverting to system image, and verify theme colors are assigned properly. Bug: 193044118 Change-Id: Idcdf5d41ee422d5a1b12cec415da9198b8405a5a --- .../theme/ThemeOverlayController.java | 23 +++++++---- .../theme/ThemeOverlayControllerTest.java | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 11ddbd045cd40..81999b534046d 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -450,19 +450,23 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { try { - int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + String colorString = systemPalette.getPackageName().toLowerCase(); + if (!colorString.startsWith("#")) { + colorString = "#" + colorString; + } + int color = Color.parseColor(colorString); mNeutralOverlay = getOverlay(color, NEUTRAL); mNeedsOverlayCreation = true; categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); - } catch (NumberFormatException e) { - Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); + } catch (Exception e) { + // Color.parseColor doesn't catch any exceptions from the calls it makes + Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName(), e); } } else if (!mIsMonetEnabled && systemPalette != null) { try { // It's possible that we flipped the flag off and still have a @ColorInt in the // setting. We need to sanitize the input, otherwise the overlay transaction will // fail. - Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); } catch (NumberFormatException e) { // This is a package name. All good, let's continue @@ -473,12 +477,17 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR); if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) { try { - int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + String colorString = accentPalette.getPackageName().toLowerCase(); + if (!colorString.startsWith("#")) { + colorString = "#" + colorString; + } + int color = Color.parseColor(colorString); mSecondaryOverlay = getOverlay(color, ACCENT); mNeedsOverlayCreation = true; categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); - } catch (NumberFormatException e) { - Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); + } catch (Exception e) { + // Color.parseColor doesn't catch any exceptions from the calls it makes + Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName(), e); } } else if (!mIsMonetEnabled && accentPalette != null) { try { diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 1a24c113a0df7..07d3fc20983f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -465,6 +465,44 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { any()); } + @Test + public void catchException_whenPackageNameIsOverlayName() { + mDeviceProvisionedController = mock(DeviceProvisionedController.class); + mThemeOverlayApplier = mock(ThemeOverlayApplier.class); + mWallpaperManager = mock(WallpaperManager.class); + + // Assume we have some wallpaper colors at boot. + when(mWallpaperManager.getWallpaperColors(anyInt())) + .thenReturn(new WallpaperColors(Color.valueOf(Color.GRAY), null, null)); + + Executor executor = MoreExecutors.directExecutor(); + + mThemeOverlayController = new ThemeOverlayController(null /* context */, + mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, + mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, + mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { + @Nullable + @Override + protected FabricatedOverlay getOverlay(int color, int type) { + FabricatedOverlay overlay = mock(FabricatedOverlay.class); + when(overlay.getIdentifier()) + .thenReturn(new OverlayIdentifier("com.thebest.livewallpaperapp.ever")); + + return overlay; + } + + }; + mThemeOverlayController.start(); + + verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), + eq(UserHandle.USER_ALL)); + verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); + + // Colors were applied during controller initialization. + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + clearInvocations(mThemeOverlayApplier); + } + @Test public void onWallpaperColorsChanged_defersUntilSetupIsCompleted_ifHasColors() { mDeviceProvisionedController = mock(DeviceProvisionedController.class);