Create ThemeOverlayController in SystemUI.

This service runs as user 0 and applies the current users' theme
customization overlay settings to user 0, associated work profiles, and
user 0 for packages that affect systemui

Bug: 126238021
Bug: 124426955
Test: unit tests; switch to secondary user and guest user and change overlays
Change-Id: I35dd9113eab1b1c1cfc81f0b38aa04900fa740dd
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce04638..a067cd2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -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. -->
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
new file mode 100644
index 0000000..f318f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -0,0 +1,123 @@
+/*
+ * 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);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
new file mode 100644
index 0000000..1a9fd53
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
@@ -0,0 +1,161 @@
+/*
+ * 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);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
new file mode 100644
index 0000000..da039a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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);
+    }
+}