diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index b3e60332fe548..7127f26a7ed2d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -23,6 +23,7 @@ import android.annotation.SuppressLint; import android.app.INotificationManager; import android.content.Context; import android.content.SharedPreferences; +import android.content.om.OverlayManager; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.HandlerThread; @@ -43,6 +44,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.accessibility.ModeSwitchesController; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; @@ -78,6 +80,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.theme.ThemeOverlayApplier; import com.android.systemui.util.leak.LeakDetector; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.splitscreen.SplitScreen; @@ -193,6 +196,17 @@ public class DependencyProvider { return new PluginManagerImpl(context, new PluginInitializerImpl()); } + /** */ + @SysUISingleton + @Provides + static ThemeOverlayApplier provideThemeOverlayManager(Context context, + @Background Executor bgExecutor, OverlayManager overlayManager, + DumpManager dumpManager) { + return new ThemeOverlayApplier(overlayManager, bgExecutor, + context.getString(R.string.launcher_overlayable_package), + context.getString(R.string.themepicker_overlayable_package), dumpManager); + } + /** */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 9705551d2e33a..c3f2e1848abd9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -31,6 +31,7 @@ import android.app.role.RoleManager; import android.app.trust.TrustManager; import android.content.ContentResolver; import android.content.Context; +import android.content.om.OverlayManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; @@ -351,7 +352,7 @@ public class FrameworkServicesModule { @Provides static WallpaperManager provideWallpaperManager(Context context) { - return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE); + return context.getSystemService(WallpaperManager.class); } @Provides @@ -361,6 +362,12 @@ public class FrameworkServicesModule { return context.getSystemService(WifiManager.class); } + @Provides + @Singleton + static OverlayManager provideOverlayManager(Context context) { + return context.getSystemService(OverlayManager.class); + } + @Provides @Singleton static WindowManager provideWindowManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java similarity index 64% rename from packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java rename to packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 665cb6307b6ae..0aa2a739eb82e 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -21,11 +21,18 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; + import com.google.android.collect.Lists; import com.google.android.collect.Sets; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -34,9 +41,19 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; -class ThemeOverlayManager { +/** + * Responsible for orchestrating overlays, based on user preferences and other inputs from + * {@link ThemeOverlayController}. + */ +@SysUISingleton +public class ThemeOverlayApplier implements Dumpable { private static final String TAG = "ThemeOverlayManager"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color"; + @VisibleForTesting + static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color"; @VisibleForTesting static final String ANDROID_PACKAGE = "android"; @@ -46,7 +63,11 @@ class ThemeOverlayManager { static final String SYSUI_PACKAGE = "com.android.systemui"; @VisibleForTesting - static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color"; + static final String OVERLAY_CATEGORY_ACCENT_COLOR = + "android.theme.customization.accent_color"; + @VisibleForTesting + static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = + "android.theme.customization.system_palette"; @VisibleForTesting static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; @VisibleForTesting @@ -73,24 +94,36 @@ class ThemeOverlayManager { * starts with launcher and grouped by target package. */ static final List THEME_CATEGORIES = Lists.newArrayList( + OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ICON_LAUNCHER, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_FONT, - OVERLAY_CATEGORY_COLOR, + OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI, OVERLAY_CATEGORY_ICON_SETTINGS, OVERLAY_CATEGORY_ICON_THEME_PICKER); - /* Categories that need to applied to the current user as well as the system user. */ + /* Categories that need to be applied to the current user as well as the system user. */ @VisibleForTesting static final Set SYSTEM_USER_CATEGORIES = Sets.newHashSet( - OVERLAY_CATEGORY_COLOR, + OVERLAY_CATEGORY_SYSTEM_PALETTE, + OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI); + /** + * List of main colors of Monet themes. These are extracted from overlays installed + * on the system. + */ + private final ArrayList mMainSystemColors = new ArrayList<>(); + /** + * Same as above, but providing accent colors instead of a system palette. + */ + private final ArrayList mAccentColors = new ArrayList<>(); + /* Allowed overlay categories for each target package. */ private final Map> mTargetPackageToCategories = new ArrayMap<>(); /* Target package for each overlay category. */ @@ -100,15 +133,15 @@ class ThemeOverlayManager { private final String mLauncherPackage; private final String mThemePickerPackage; - ThemeOverlayManager(OverlayManager overlayManager, Executor executor, - String launcherPackage, String themePickerPackage) { + public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor, + String launcherPackage, String themePickerPackage, DumpManager dumpManager) { mOverlayManager = overlayManager; mExecutor = executor; mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( - OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT, - OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID)); + OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, + OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID)); mTargetPackageToCategories.put(SYSUI_PACKAGE, Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI)); mTargetPackageToCategories.put(SETTINGS_PACKAGE, @@ -117,7 +150,7 @@ class ThemeOverlayManager { Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER)); mTargetPackageToCategories.put(mThemePickerPackage, Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER)); - mCategoryToTargetPackage.put(OVERLAY_CATEGORY_COLOR, ANDROID_PACKAGE); + mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE); @@ -125,6 +158,54 @@ class ThemeOverlayManager { mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage); + + collectMonetSystemOverlays(); + dumpManager.registerDumpable(TAG, this); + } + + /** + * List of accent colors available as Monet overlays. + */ + List getAvailableAccentColors() { + return mAccentColors; + } + + /** + * List of main system colors available as Monet overlays. + */ + List getAvailableSystemColors() { + return mMainSystemColors; + } + + private void collectMonetSystemOverlays() { + List androidOverlays = mOverlayManager + .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM); + for (OverlayInfo overlayInfo : androidOverlays) { + String packageName = overlayInfo.packageName; + if (DEBUG) { + Log.d(TAG, "Processing overlay " + packageName); + } + if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category) + && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) { + try { + String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, ""); + mMainSystemColors.add(Integer.parseInt(color, 16)); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid package name for overlay " + packageName, e); + } + } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category) + && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) { + try { + String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, ""); + mAccentColors.add(Integer.parseInt(color, 16)); + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid package name for overlay " + packageName, e); + } + } else if (DEBUG) { + Log.d(TAG, "Unknown overlay: " + packageName + " category: " + + overlayInfo.category); + } + } } /** @@ -184,4 +265,13 @@ class ThemeOverlayManager { } }); } + + /** + * @inherit + */ + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mMainSystemColors=" + mMainSystemColors.size()); + pw.println("mAccentColors=" + mAccentColors.size()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 132e092b8adad..c4e2b5d47ba8d 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -15,16 +15,20 @@ */ package com.android.systemui.theme; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; + import android.app.ActivityManager; +import android.app.WallpaperColors; +import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.om.OverlayManager; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.graphics.Color; import android.net.Uri; -import android.os.AsyncTask; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; @@ -33,20 +37,32 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import com.android.systemui.R; +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.SecureSettings; import com.google.android.collect.Sets; import org.json.JSONException; import org.json.JSONObject; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -60,49 +76,76 @@ import javax.inject.Inject; * associated work profiles */ @SysUISingleton -public class ThemeOverlayController extends SystemUI { +public class ThemeOverlayController extends SystemUI implements Dumpable { private static final String TAG = "ThemeOverlayController"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private ThemeOverlayManager mThemeManager; - private UserManager mUserManager; - private BroadcastDispatcher mBroadcastDispatcher; + // If lock screen wallpaper colors should also be considered when selecting the theme. + // Doing this has performance impact, given that overlays would need to be swapped when + // the device unlocks. + @VisibleForTesting + static final boolean USE_LOCK_SCREEN_WALLPAPER = false; + + private final ThemeOverlayApplier mThemeManager; + private final UserManager mUserManager; + private final BroadcastDispatcher mBroadcastDispatcher; + private final Executor mBgExecutor; + private final SecureSettings mSecureSettings; + private final Executor mMainExecutor; private final Handler mBgHandler; + private final WallpaperManager mWallpaperManager; + private final KeyguardStateController mKeyguardStateController; + private WallpaperColors mLockColors; + private WallpaperColors mSystemColors; + // Color extracted from wallpaper, NOT the color used on the overlay + private int mMainWallpaperColor = Color.TRANSPARENT; + // Color extracted from wallpaper, NOT the color used on the overlay + private int mWallpaperAccentColor = Color.TRANSPARENT; + // Main system color that maps to an overlay color + private int mSystemOverlayColor = Color.TRANSPARENT; + // Accent color that maps to an overlay color + private int mAccentOverlayColor = Color.TRANSPARENT; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, - @Background Handler bgHandler) { + @Background Handler bgHandler, @Main Executor mainExecutor, + @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier, + SecureSettings secureSettings, WallpaperManager wallpaperManager, + UserManager userManager, KeyguardStateController keyguardStateController, + DumpManager dumpManager) { super(context); + mBroadcastDispatcher = broadcastDispatcher; + mUserManager = userManager; + mBgExecutor = bgExecutor; + mMainExecutor = mainExecutor; mBgHandler = bgHandler; + mThemeManager = themeOverlayApplier; + mSecureSettings = secureSettings; + mWallpaperManager = wallpaperManager; + mKeyguardStateController = keyguardStateController; + dumpManager.registerDumpable(TAG, this); } @Override public void start() { if (DEBUG) Log.d(TAG, "Start"); - mUserManager = mContext.getSystemService(UserManager.class); - mThemeManager = new ThemeOverlayManager( - mContext.getSystemService(OverlayManager.class), - AsyncTask.THREAD_POOL_EXECUTOR, - mContext.getString(R.string.launcher_overlayable_package), - mContext.getString(R.string.themepicker_overlayable_package)); final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() { + mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); updateThemeOverlays(); } - }, filter, mBgHandler, UserHandle.ALL); - mContext.getContentResolver().registerContentObserver( + }, filter, mBgExecutor, UserHandle.ALL); + mSecureSettings.registerContentObserverForUser( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, new ContentObserver(mBgHandler) { - @Override - public void onChange(boolean selfChange, Collection uris, int flags, + public void onChange(boolean selfChange, Collection collection, int flags, int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { @@ -111,20 +154,147 @@ public class ThemeOverlayController extends SystemUI { } }, UserHandle.USER_ALL); + + // Upon boot, make sure we have the most up to date colors + mBgExecutor.execute(() -> { + WallpaperColors lockColors = mWallpaperManager.getWallpaperColors( + WallpaperManager.FLAG_LOCK); + WallpaperColors systemColor = mWallpaperManager.getWallpaperColors( + WallpaperManager.FLAG_SYSTEM); + mMainExecutor.execute(() -> { + if (USE_LOCK_SCREEN_WALLPAPER) { + mLockColors = lockColors; + } + mSystemColors = systemColor; + reevaluateSystemTheme(); + }); + }); + if (USE_LOCK_SCREEN_WALLPAPER) { + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + if (mLockColors == null) { + return; + } + // It's possible that the user has a lock screen wallpaper. On this case we'll + // end up with different colors after unlocking. + reevaluateSystemTheme(); + } + }); + } + mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> { + if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) { + mLockColors = wallpaperColors; + if (DEBUG) { + Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); + } + } + if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { + mSystemColors = wallpaperColors; + if (DEBUG) { + Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); + } + } + reevaluateSystemTheme(); + }, null, UserHandle.USER_ALL); + } + + private void reevaluateSystemTheme() { + if (mLockColors == null && mSystemColors == null) { + Log.w(TAG, "Cannot update theme, colors are null"); + return; + } + WallpaperColors currentColor = + mKeyguardStateController.isShowing() && mLockColors != null + ? mLockColors : mSystemColors; + int mainColor = currentColor.getPrimaryColor().toArgb(); + + //TODO(b/172860591) implement more complex logic for picking accent color. + //For now, picking the secondary should be enough. + Color accentCandidate = currentColor.getSecondaryColor(); + if (accentCandidate == null) { + accentCandidate = currentColor.getTertiaryColor(); + } + if (accentCandidate == null) { + accentCandidate = currentColor.getPrimaryColor(); + } + + if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate.toArgb()) { + return; + } + + mMainWallpaperColor = mainColor; + mWallpaperAccentColor = accentCandidate.toArgb(); + + // Let's compare these colors to our finite set of overlays, and then pick an overlay. + List systemColors = mThemeManager.getAvailableSystemColors(); + List accentColors = mThemeManager.getAvailableAccentColors(); + + if (systemColors.size() == 0 || accentColors.size() == 0) { + if (DEBUG) { + Log.d(TAG, "Cannot apply system theme, palettes are unavailable"); + } + return; + } + + mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor); + mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor); + + updateThemeOverlays(); + } + + /** + * Given a color and a list of candidates, return the candidate that's the most similar to the + * given color. + */ + private static int getClosest(List candidates, int color) { + float[] hslMain = new float[3]; + float[] hslCandidate = new float[3]; + + ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain); + hslMain[0] /= 360f; + + float minDistance = Float.MAX_VALUE; + int closestColor = Color.TRANSPARENT; + for (int candidate: candidates) { + ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate), + hslCandidate); + hslCandidate[0] /= 360f; + + float sqDistance = squared(hslCandidate[0] - hslMain[0]) + + squared(hslCandidate[1] - hslMain[1]) + + squared(hslCandidate[2] - hslMain[2]); + if (sqDistance < minDistance) { + minDistance = sqDistance; + closestColor = candidate; + } + } + return closestColor; + } + + private static float squared(float f) { + return f * f; } private void updateThemeOverlays() { final int currentUser = ActivityManager.getCurrentUser(); - final String overlayPackageJson = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + final String overlayPackageJson = mSecureSettings.getStringForUser( + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays: " + overlayPackageJson); + boolean hasSystemPalette = false; + boolean hasAccentColor = false; final Map categoryToPackage = new ArrayMap<>(); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); - for (String category : ThemeOverlayManager.THEME_CATEGORIES) { + for (String category : ThemeOverlayApplier.THEME_CATEGORIES) { if (object.has(category)) { + if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) { + hasAccentColor = true; + } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) { + hasSystemPalette = true; + } categoryToPackage.put(category, object.getString(category)); } } @@ -132,6 +302,20 @@ public class ThemeOverlayController extends SystemUI { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } + + // Let's apply the system palette, but only if it was not overridden by the style picker. + if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) { + categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, + ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE + + Integer.toHexString(mSystemOverlayColor).toUpperCase()); + } + // Same for the accent color + if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) { + categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, + ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE + + Integer.toHexString(mAccentOverlayColor).toUpperCase()); + } + Set userHandles = Sets.newHashSet(UserHandle.of(currentUser)); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { @@ -140,4 +324,14 @@ public class ThemeOverlayController extends SystemUI { } mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles); } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mLockColors=" + mLockColors); + pw.println("mSystemColors=" + mSystemColors); + pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); + pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); + pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor)); + pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java similarity index 85% rename from packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java rename to packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index c99deb606459c..edaff5ff0f101 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -15,22 +15,24 @@ */ package com.android.systemui.theme; -import static com.android.systemui.theme.ThemeOverlayManager.ANDROID_PACKAGE; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_COLOR; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_FONT; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_ANDROID; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_LAUNCHER; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SETTINGS; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SYSUI; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_THEME_PICKER; -import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_SHAPE; -import static com.android.systemui.theme.ThemeOverlayManager.SETTINGS_PACKAGE; -import static com.android.systemui.theme.ThemeOverlayManager.SYSTEM_USER_CATEGORIES; -import static com.android.systemui.theme.ThemeOverlayManager.SYSUI_PACKAGE; -import static com.android.systemui.theme.ThemeOverlayManager.THEME_CATEGORIES; +import static com.android.systemui.theme.ThemeOverlayApplier.ANDROID_PACKAGE; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_FONT; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_ANDROID; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_LAUNCHER; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; +import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE; +import static com.android.systemui.theme.ThemeOverlayApplier.SYSTEM_USER_CATEGORIES; +import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE; +import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +46,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.google.android.collect.Maps; import com.google.common.collect.Lists; @@ -63,7 +66,7 @@ import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class ThemeOverlayManagerTest extends SysuiTestCase { +public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String TEST_DISABLED_PREFIX = "com.example."; private static final String TEST_ENABLED_PREFIX = "com.example.enabled."; @@ -82,26 +85,32 @@ public class ThemeOverlayManagerTest extends SysuiTestCase { @Mock OverlayManager mOverlayManager; + @Mock + DumpManager mDumpManager; - private ThemeOverlayManager mManager; + private ThemeOverlayApplier mManager; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); - mManager = new ThemeOverlayManager(mOverlayManager, MoreExecutors.directExecutor(), - LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE); + mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(), + LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager); when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( - createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_COLOR, - ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, false), + createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR, + ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false), + createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE, + ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, false), createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, false), - createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_COLOR, - ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, true), + createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR, + ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true), + createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE, + ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, @@ -132,6 +141,8 @@ public class ThemeOverlayManagerTest extends SysuiTestCase { THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER, THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true))); + clearInvocations(mOverlayManager); + verify(mDumpManager).registerDumpable(any(), any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java new file mode 100644 index 0000000000000..aee884022d95f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.theme; + +import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE; +import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; +import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.WallpaperColors; +import android.app.WallpaperManager; +import android.graphics.Color; +import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ThemeOverlayControllerTest extends SysuiTestCase { + + private ThemeOverlayController mThemeOverlayController; + @Mock + private Executor mBgExecutor; + @Mock + private Executor mMainExecutor; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private Handler mBgHandler; + @Mock + private ThemeOverlayApplier mThemeOverlayApplier; + @Mock + private SecureSettings mSecureSettings; + @Mock + private WallpaperManager mWallpaperManager; + @Mock + private UserManager mUserManager; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private DumpManager mDumpManager; + @Captor + private ArgumentCaptor mKeyguardStateControllerCallback; + @Captor + private ArgumentCaptor mColorsListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mThemeOverlayController = new ThemeOverlayController(null /* context */, + mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, + mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController, + mDumpManager); + + mThemeOverlayController.start(); + if (USE_LOCK_SCREEN_WALLPAPER) { + verify(mKeyguardStateController).addCallback( + mKeyguardStateControllerCallback.capture()); + } + verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), + eq(UserHandle.USER_ALL)); + verify(mDumpManager).registerDumpable(any(), any()); + + List colorList = List.of(Color.RED, Color.BLUE); + when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList); + when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList); + } + + @Test + public void start_checksWallpaper() { + ArgumentCaptor registrationRunnable = ArgumentCaptor.forClass(Runnable.class); + verify(mBgExecutor).execute(registrationRunnable.capture()); + + registrationRunnable.getValue().run(); + verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK)); + verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM)); + } + + @Test + public void onWallpaperColorsChanged_setsTheme() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); + ArgumentCaptor> themeOverlays = ArgumentCaptor.forClass(Map.class); + + verify(mThemeOverlayApplier).getAvailableSystemColors(); + verify(mThemeOverlayApplier).getAvailableAccentColors(); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); + + // Assert that we received the colors that we were expecting + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) + .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + + Integer.toHexString(Color.RED).toUpperCase()); + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) + .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + + Integer.toHexString(Color.BLUE).toUpperCase()); + + // Should not ask again if changed to same value + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); + verifyNoMoreInteractions(mThemeOverlayApplier); + } + + @Test + public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + + String jsonString = + "{\"android.theme.customization.system_palette\":\"override.package.name\"}"; + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); + ArgumentCaptor> themeOverlays = ArgumentCaptor.forClass(Map.class); + + verify(mThemeOverlayApplier).getAvailableSystemColors(); + verify(mThemeOverlayApplier).getAvailableAccentColors(); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any()); + + // Assert that we received the colors that we were expecting + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) + .isEqualTo("override.package.name"); + } +}