Loading packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -316,6 +316,7 @@ <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.SizeCompatModeActivityController</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> Loading Loading @@ -486,4 +487,7 @@ <integer name="ongoing_appops_dialog_max_apps">5</integer> <!-- Launcher package name for overlaying icons. --> <string name="launcher_overlayable_package" translatable="false">com.android.launcher3</string> </resources> packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.app.ActivityManager; 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.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.google.android.collect.Sets; import org.json.JSONException; import org.json.JSONObject; import java.util.Map; import java.util.Set; /** * Controls the application of theme overlays across the system for all users. * This service is responsible for: * - Observing changes to Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES and applying the * corresponding overlays across the system * - Observing user switches, applying the overlays for the current user to user 0 (for systemui) * - Observing work profile changes and applying overlays from the primary user to their * associated work profiles */ public class ThemeOverlayController extends SystemUI { private static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = false; private ThemeOverlayManager mThemeManager; private UserManager mUserManager; @Override public void start() { if (DEBUG) Log.d(TAG, "Start"); mUserManager = mContext.getSystemService(UserManager.class); mThemeManager = new ThemeOverlayManager( mContext.getSystemService(OverlayManager.class), mContext.getString(R.string.launcher_overlayable_package)); final Handler bgHandler = Dependency.get(Dependency.BG_HANDLER); final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); updateThemeOverlays(); } }, UserHandle.ALL, filter, null, bgHandler); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, new ContentObserver(bgHandler) { @Override public void onChange(boolean selfChange, Uri uri, int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { updateThemeOverlays(); } } }, UserHandle.USER_ALL); } private void updateThemeOverlays() { final int currentUser = ActivityManager.getCurrentUser(); final String overlayPackageJson = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays: " + overlayPackageJson); final Map<String, String> categoryToPackage = new ArrayMap<>(); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); for (String category : ThemeOverlayManager.THEME_CATEGORIES) { if (object.has(category)) { categoryToPackage.put(category, object.getString(category)); } } } catch (JSONException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { userHandles.add(userInfo.getUserHandle()); } } mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles); } } packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.google.android.collect.Sets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; class ThemeOverlayManager { private static final String TAG = "ThemeOverlayManager"; private static final boolean DEBUG = false; @VisibleForTesting static final String ANDROID_PACKAGE = "android"; @VisibleForTesting static final String SETTINGS_PACKAGE = "com.android.settings"; @VisibleForTesting static final String SYSUI_PACKAGE = "com.android.systemui"; @VisibleForTesting static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color"; @VisibleForTesting static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; @VisibleForTesting static final String OVERLAY_CATEGORY_SHAPE = "android.theme.customization.adaptive_icon_shape"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_ANDROID = "android.theme.customization.icon_pack.android"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_SYSUI = "android.theme.customization.icon_pack.systemui"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_SETTINGS = "android.theme.customization.icon_pack.settings"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_LAUNCHER = "android.theme.customization.icon_pack.launcher"; /* All theme customization categories used by the system. */ static final Set<String> THEME_CATEGORIES = Sets.newHashSet( OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI, OVERLAY_CATEGORY_ICON_SETTINGS, OVERLAY_CATEGORY_ICON_LAUNCHER); /* Categories that need to 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_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI); /* Allowed overlay categories for each target package. */ private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>(); /* Target package for each overlay category. */ private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); private final OverlayManager mOverlayManager; private final String mLauncherPackage; ThemeOverlayManager(OverlayManager overlayManager, String launcherPackage) { mOverlayManager = overlayManager; mLauncherPackage = launcherPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( OVERLAY_CATEGORY_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, Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS)); mTargetPackageToCategories.put(mLauncherPackage, Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER)); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_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); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); } /** * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that * affect sysui will also be applied to the system user. */ void applyCurrentUserOverlays( Map<String, String> categoryToPackage, Set<UserHandle> userHandles) { final Map<Boolean, List<String>> categorySplit = THEME_CATEGORIES.stream().collect( Collectors.partitioningBy((category) -> categoryToPackage.containsKey(category))); final List<String> overlayCategoriesToEnable = categorySplit.get(true); final List<String> overlayCategoriesToDisable = categorySplit.get(false); // Disable all overlays that have not been specified in the user setting. final List<OverlayInfo> overlays = new ArrayList<>(); overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()) .forEach(targetPackage -> overlays.addAll(mOverlayManager .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM))); overlays.stream() .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) .filter(o -> o.isEnabled()) .forEach(o -> setEnabled(o.packageName, o.category, userHandles, false)); // Enable all overlays specified in the user setting. overlayCategoriesToEnable.forEach((category) -> setEnabled(categoryToPackage.get(category), category, userHandles, true)); } private void setEnabled( String packageName, String category, Set<UserHandle> handles, boolean enabled) { for (UserHandle userHandle : handles) { setEnabled(packageName, userHandle, enabled); } if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { setEnabled(packageName, UserHandle.SYSTEM, enabled); } } private void setEnabled(String pkg, UserHandle userHandle, boolean enabled) { if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled)); if (enabled) { mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle); } else { mOverlayManager.setEnabled(pkg, false, userHandle); } } } packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java 0 → 100644 +230 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.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_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 org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.google.android.collect.Maps; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.Map; import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class ThemeOverlayManagerTest extends SysuiTestCase { private static final String TEST_DISABLED_PREFIX = "com.example."; private static final String TEST_ENABLED_PREFIX = "com.example.enabled."; private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap(); static { for (String category : THEME_CATEGORIES) { ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category); } } private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; private static final UserHandle TEST_USER = UserHandle.of(5); private static final Set<UserHandle> TEST_USER_HANDLES = Sets.newHashSet(TEST_USER); @Mock OverlayManager mOverlayManager; private ThemeOverlayManager mManager; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); mManager = new ThemeOverlayManager(mOverlayManager, LAUNCHER_PACKAGE); 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_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_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, true))); when(mOverlayManager.getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI, true))); when(mOverlayManager.getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS, true))); when(mOverlayManager.getOverlayInfosForTarget(LAUNCHER_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_LAUNCHER, LAUNCHER_PACKAGE, OVERLAY_CATEGORY_ICON_LAUNCHER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_LAUNCHER, LAUNCHER_PACKAGE, OVERLAY_CATEGORY_ICON_LAUNCHER, true))); } @Test public void allCategoriesSpecified_allEnabledExclusively() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); } } @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { verify(mOverlayManager).setEnabledExclusiveInCategory( entry.getValue(), UserHandle.SYSTEM); } else { verify(mOverlayManager, never()).setEnabledExclusiveInCategory( entry.getValue(), UserHandle.SYSTEM); } } } @Test public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); UserHandle newUserHandle = UserHandle.of(10); userHandles.add(newUserHandle); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles); for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle); } } @Test public void allCategoriesSpecified_overlayManagerNotQueried() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); verify(mOverlayManager, never()) .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); } @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); for (String overlayPackage : categoryToPackage.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); } verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, false, TEST_USER); verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, false, TEST_USER); } @Test public void zeroCategoriesSpecified_allDisabled() { mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER); } } @Test public void nonThemeCategorySpecified_ignored() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.put("blah.category", "com.example.blah.category"); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER); verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category", TEST_USER); } @Test public void overlayManagerOnlyQueriedForUnspecifiedPackages() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(LAUNCHER_PACKAGE, UserHandle.SYSTEM); } private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { return new OverlayInfo(packageName, targetPackageName, null, category, "", enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false); } } Loading
packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -316,6 +316,7 @@ <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.SizeCompatModeActivityController</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> Loading Loading @@ -486,4 +487,7 @@ <integer name="ongoing_appops_dialog_max_apps">5</integer> <!-- Launcher package name for overlaying icons. --> <string name="launcher_overlayable_package" translatable="false">com.android.launcher3</string> </resources>
packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.app.ActivityManager; 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.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.google.android.collect.Sets; import org.json.JSONException; import org.json.JSONObject; import java.util.Map; import java.util.Set; /** * Controls the application of theme overlays across the system for all users. * This service is responsible for: * - Observing changes to Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES and applying the * corresponding overlays across the system * - Observing user switches, applying the overlays for the current user to user 0 (for systemui) * - Observing work profile changes and applying overlays from the primary user to their * associated work profiles */ public class ThemeOverlayController extends SystemUI { private static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = false; private ThemeOverlayManager mThemeManager; private UserManager mUserManager; @Override public void start() { if (DEBUG) Log.d(TAG, "Start"); mUserManager = mContext.getSystemService(UserManager.class); mThemeManager = new ThemeOverlayManager( mContext.getSystemService(OverlayManager.class), mContext.getString(R.string.launcher_overlayable_package)); final Handler bgHandler = Dependency.get(Dependency.BG_HANDLER); final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); updateThemeOverlays(); } }, UserHandle.ALL, filter, null, bgHandler); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), false, new ContentObserver(bgHandler) { @Override public void onChange(boolean selfChange, Uri uri, int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { updateThemeOverlays(); } } }, UserHandle.USER_ALL); } private void updateThemeOverlays() { final int currentUser = ActivityManager.getCurrentUser(); final String overlayPackageJson = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays: " + overlayPackageJson); final Map<String, String> categoryToPackage = new ArrayMap<>(); if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); for (String category : ThemeOverlayManager.THEME_CATEGORIES) { if (object.has(category)) { categoryToPackage.put(category, object.getString(category)); } } } catch (JSONException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { userHandles.add(userInfo.getUserHandle()); } } mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles); } }
packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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 android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.google.android.collect.Sets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; class ThemeOverlayManager { private static final String TAG = "ThemeOverlayManager"; private static final boolean DEBUG = false; @VisibleForTesting static final String ANDROID_PACKAGE = "android"; @VisibleForTesting static final String SETTINGS_PACKAGE = "com.android.settings"; @VisibleForTesting static final String SYSUI_PACKAGE = "com.android.systemui"; @VisibleForTesting static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color"; @VisibleForTesting static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; @VisibleForTesting static final String OVERLAY_CATEGORY_SHAPE = "android.theme.customization.adaptive_icon_shape"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_ANDROID = "android.theme.customization.icon_pack.android"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_SYSUI = "android.theme.customization.icon_pack.systemui"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_SETTINGS = "android.theme.customization.icon_pack.settings"; @VisibleForTesting static final String OVERLAY_CATEGORY_ICON_LAUNCHER = "android.theme.customization.icon_pack.launcher"; /* All theme customization categories used by the system. */ static final Set<String> THEME_CATEGORIES = Sets.newHashSet( OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI, OVERLAY_CATEGORY_ICON_SETTINGS, OVERLAY_CATEGORY_ICON_LAUNCHER); /* Categories that need to 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_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SYSUI); /* Allowed overlay categories for each target package. */ private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>(); /* Target package for each overlay category. */ private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); private final OverlayManager mOverlayManager; private final String mLauncherPackage; ThemeOverlayManager(OverlayManager overlayManager, String launcherPackage) { mOverlayManager = overlayManager; mLauncherPackage = launcherPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( OVERLAY_CATEGORY_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, Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS)); mTargetPackageToCategories.put(mLauncherPackage, Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER)); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_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); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE); mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); } /** * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that * affect sysui will also be applied to the system user. */ void applyCurrentUserOverlays( Map<String, String> categoryToPackage, Set<UserHandle> userHandles) { final Map<Boolean, List<String>> categorySplit = THEME_CATEGORIES.stream().collect( Collectors.partitioningBy((category) -> categoryToPackage.containsKey(category))); final List<String> overlayCategoriesToEnable = categorySplit.get(true); final List<String> overlayCategoriesToDisable = categorySplit.get(false); // Disable all overlays that have not been specified in the user setting. final List<OverlayInfo> overlays = new ArrayList<>(); overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()) .forEach(targetPackage -> overlays.addAll(mOverlayManager .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM))); overlays.stream() .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) .filter(o -> o.isEnabled()) .forEach(o -> setEnabled(o.packageName, o.category, userHandles, false)); // Enable all overlays specified in the user setting. overlayCategoriesToEnable.forEach((category) -> setEnabled(categoryToPackage.get(category), category, userHandles, true)); } private void setEnabled( String packageName, String category, Set<UserHandle> handles, boolean enabled) { for (UserHandle userHandle : handles) { setEnabled(packageName, userHandle, enabled); } if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { setEnabled(packageName, UserHandle.SYSTEM, enabled); } } private void setEnabled(String pkg, UserHandle userHandle, boolean enabled) { if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled)); if (enabled) { mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle); } else { mOverlayManager.setEnabled(pkg, false, userHandle); } } }
packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java 0 → 100644 +230 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.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_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 org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.google.android.collect.Maps; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.Map; import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class ThemeOverlayManagerTest extends SysuiTestCase { private static final String TEST_DISABLED_PREFIX = "com.example."; private static final String TEST_ENABLED_PREFIX = "com.example.enabled."; private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap(); static { for (String category : THEME_CATEGORIES) { ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category); } } private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; private static final UserHandle TEST_USER = UserHandle.of(5); private static final Set<UserHandle> TEST_USER_HANDLES = Sets.newHashSet(TEST_USER); @Mock OverlayManager mOverlayManager; private ThemeOverlayManager mManager; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); mManager = new ThemeOverlayManager(mOverlayManager, LAUNCHER_PACKAGE); 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_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_FONT, ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, true), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, true))); when(mOverlayManager.getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI, true))); when(mOverlayManager.getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS, true))); when(mOverlayManager.getOverlayInfosForTarget(LAUNCHER_PACKAGE, UserHandle.SYSTEM)) .thenReturn(Lists.newArrayList( createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_LAUNCHER, LAUNCHER_PACKAGE, OVERLAY_CATEGORY_ICON_LAUNCHER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_LAUNCHER, LAUNCHER_PACKAGE, OVERLAY_CATEGORY_ICON_LAUNCHER, true))); } @Test public void allCategoriesSpecified_allEnabledExclusively() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); } } @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { verify(mOverlayManager).setEnabledExclusiveInCategory( entry.getValue(), UserHandle.SYSTEM); } else { verify(mOverlayManager, never()).setEnabledExclusiveInCategory( entry.getValue(), UserHandle.SYSTEM); } } } @Test public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); UserHandle newUserHandle = UserHandle.of(10); userHandles.add(newUserHandle); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles); for (String overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle); } } @Test public void allCategoriesSpecified_overlayManagerNotQueried() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES); verify(mOverlayManager, never()) .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); } @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); for (String overlayPackage : categoryToPackage.values()) { verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER); } verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS, false, TEST_USER); verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID, false, TEST_USER); } @Test public void zeroCategoriesSpecified_allDisabled() { mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER); } } @Test public void nonThemeCategorySpecified_ignored() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.put("blah.category", "com.example.blah.category"); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER); verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category", TEST_USER); } @Test public void overlayManagerOnlyQueriedForUnspecifiedPackages() { Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES); verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM); verify(mOverlayManager, never()).getOverlayInfosForTarget(LAUNCHER_PACKAGE, UserHandle.SYSTEM); } private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { return new OverlayInfo(packageName, targetPackageName, null, category, "", enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false); } }