Merge "Let wallpaper colors drive system theme"

This commit is contained in:
Lucas Dupin
2020-11-11 21:31:26 +00:00
committed by Android (Google) Code Review
6 changed files with 545 additions and 56 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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<String> 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<String> 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<Integer> mMainSystemColors = new ArrayList<>();
/**
* Same as above, but providing accent colors instead of a system palette.
*/
private final ArrayList<Integer> mAccentColors = new ArrayList<>();
/* Allowed overlay categories for each target package. */
private final Map<String, Set<String>> 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<Integer> getAvailableAccentColors() {
return mAccentColors;
}
/**
* List of main system colors available as Monet overlays.
*/
List<Integer> getAvailableSystemColors() {
return mMainSystemColors;
}
private void collectMonetSystemOverlays() {
List<OverlayInfo> 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());
}
}

View File

@@ -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<Uri> uris, int flags,
public void onChange(boolean selfChange, Collection<Uri> 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<Integer> systemColors = mThemeManager.getAvailableSystemColors();
List<Integer> 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<Integer> 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<String, String> 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<UserHandle> 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));
}
}

View File

@@ -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

View File

@@ -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<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
@Captor
private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> 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<Integer> colorList = List.of(Color.RED, Color.BLUE);
when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
}
@Test
public void start_checksWallpaper() {
ArgumentCaptor<Runnable> 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<Map<String, String>> 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<Map<String, String>> 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");
}
}