1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.systemui.theme;
17 
18 import android.content.om.OverlayInfo;
19 import android.content.om.OverlayManager;
20 import android.os.UserHandle;
21 import android.util.ArrayMap;
22 import android.util.Log;
23 
24 import androidx.annotation.VisibleForTesting;
25 
26 import com.google.android.collect.Lists;
27 import com.google.android.collect.Sets;
28 
29 import java.util.ArrayList;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.Executor;
35 import java.util.stream.Collectors;
36 
37 class ThemeOverlayManager {
38     private static final String TAG = "ThemeOverlayManager";
39     private static final boolean DEBUG = false;
40 
41     @VisibleForTesting
42     static final String ANDROID_PACKAGE = "android";
43     @VisibleForTesting
44     static final String SETTINGS_PACKAGE = "com.android.settings";
45     @VisibleForTesting
46     static final String SYSUI_PACKAGE = "com.android.systemui";
47 
48     @VisibleForTesting
49     static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color";
50     @VisibleForTesting
51     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
52     @VisibleForTesting
53     static final String OVERLAY_CATEGORY_SHAPE =
54             "android.theme.customization.adaptive_icon_shape";
55     @VisibleForTesting
56     static final String OVERLAY_CATEGORY_ICON_ANDROID =
57             "android.theme.customization.icon_pack.android";
58     @VisibleForTesting
59     static final String OVERLAY_CATEGORY_ICON_SYSUI =
60             "android.theme.customization.icon_pack.systemui";
61     @VisibleForTesting
62     static final String OVERLAY_CATEGORY_ICON_SETTINGS =
63             "android.theme.customization.icon_pack.settings";
64     @VisibleForTesting
65     static final String OVERLAY_CATEGORY_ICON_LAUNCHER =
66             "android.theme.customization.icon_pack.launcher";
67     @VisibleForTesting
68     static final String OVERLAY_CATEGORY_ICON_THEME_PICKER =
69             "android.theme.customization.icon_pack.themepicker";
70 
71     /*
72      * All theme customization categories used by the system, in order that they should be applied,
73      * starts with launcher and grouped by target package.
74      */
75     static final List<String> THEME_CATEGORIES = Lists.newArrayList(
76             OVERLAY_CATEGORY_ICON_LAUNCHER,
77             OVERLAY_CATEGORY_SHAPE,
78             OVERLAY_CATEGORY_FONT,
79             OVERLAY_CATEGORY_COLOR,
80             OVERLAY_CATEGORY_ICON_ANDROID,
81             OVERLAY_CATEGORY_ICON_SYSUI,
82             OVERLAY_CATEGORY_ICON_SETTINGS,
83             OVERLAY_CATEGORY_ICON_THEME_PICKER);
84 
85     /* Categories that need to applied to the current user as well as the system user. */
86     @VisibleForTesting
87     static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
88             OVERLAY_CATEGORY_COLOR,
89             OVERLAY_CATEGORY_FONT,
90             OVERLAY_CATEGORY_SHAPE,
91             OVERLAY_CATEGORY_ICON_ANDROID,
92             OVERLAY_CATEGORY_ICON_SYSUI);
93 
94     /* Allowed overlay categories for each target package. */
95     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
96     /* Target package for each overlay category. */
97     private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
98     private final OverlayManager mOverlayManager;
99     private final Executor mExecutor;
100     private final String mLauncherPackage;
101     private final String mThemePickerPackage;
102 
ThemeOverlayManager(OverlayManager overlayManager, Executor executor, String launcherPackage, String themePickerPackage)103     ThemeOverlayManager(OverlayManager overlayManager, Executor executor,
104             String launcherPackage, String themePickerPackage) {
105         mOverlayManager = overlayManager;
106         mExecutor = executor;
107         mLauncherPackage = launcherPackage;
108         mThemePickerPackage = themePickerPackage;
109         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
110                 OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT,
111                 OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID));
112         mTargetPackageToCategories.put(SYSUI_PACKAGE,
113                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
114         mTargetPackageToCategories.put(SETTINGS_PACKAGE,
115                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS));
116         mTargetPackageToCategories.put(mLauncherPackage,
117                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER));
118         mTargetPackageToCategories.put(mThemePickerPackage,
119                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER));
120         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_COLOR, ANDROID_PACKAGE);
121         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE);
122         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE);
123         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE);
124         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE);
125         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE);
126         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
127         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
128     }
129 
130     /**
131      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
132      * affect sysui will also be applied to the system user.
133      */
applyCurrentUserOverlays( Map<String, String> categoryToPackage, Set<UserHandle> userHandles)134     void applyCurrentUserOverlays(
135             Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
136         // Disable all overlays that have not been specified in the user setting.
137         final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
138         overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
139         final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream()
140                 .map(category -> mCategoryToTargetPackage.get(category))
141                 .collect(Collectors.toSet());
142         final List<OverlayInfo> overlays = new ArrayList<>();
143         targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
144                 .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
145         final Map<String, String> overlaysToDisable = overlays.stream()
146                 .filter(o ->
147                         mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
148                 .filter(o -> overlayCategoriesToDisable.contains(o.category))
149                 .filter(o -> o.isEnabled())
150                 .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
151 
152         // Toggle overlays in the order of THEME_CATEGORIES.
153         for (String category : THEME_CATEGORIES) {
154             if (categoryToPackage.containsKey(category)) {
155                 setEnabled(categoryToPackage.get(category), category, userHandles, true);
156             } else if (overlaysToDisable.containsKey(category)) {
157                 setEnabled(overlaysToDisable.get(category), category, userHandles, false);
158             }
159         }
160     }
161 
setEnabled( String packageName, String category, Set<UserHandle> handles, boolean enabled)162     private void setEnabled(
163             String packageName, String category, Set<UserHandle> handles, boolean enabled) {
164         for (UserHandle userHandle : handles) {
165             setEnabledAsync(packageName, userHandle, enabled);
166         }
167         if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
168             setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
169         }
170     }
171 
setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled)172     private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
173         mExecutor.execute(() -> {
174             if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
175             try {
176                 if (enabled) {
177                     mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
178                 } else {
179                     mOverlayManager.setEnabled(pkg, false, userHandle);
180                 }
181             } catch (IllegalStateException e) {
182                 Log.e(TAG,
183                         String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
184             }
185         });
186     }
187 }
188