• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.AnyThread;
19 import android.content.om.FabricatedOverlay;
20 import android.content.om.OverlayIdentifier;
21 import android.content.om.OverlayInfo;
22 import android.content.om.OverlayManager;
23 import android.content.om.OverlayManagerTransaction;
24 import android.os.UserHandle;
25 import android.util.ArrayMap;
26 import android.util.Log;
27 import android.util.Pair;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.systemui.Dumpable;
33 import com.android.systemui.dagger.SysUISingleton;
34 import com.android.systemui.dagger.qualifiers.Background;
35 import com.android.systemui.dump.DumpManager;
36 
37 import com.google.android.collect.Lists;
38 import com.google.android.collect.Sets;
39 
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.concurrent.Executor;
47 import java.util.stream.Collectors;
48 
49 import javax.inject.Inject;
50 import javax.inject.Named;
51 
52 /**
53  * Responsible for orchestrating overlays, based on user preferences and other inputs from
54  * {@link ThemeOverlayController}.
55  */
56 @SysUISingleton
57 public class ThemeOverlayApplier implements Dumpable {
58     private static final String TAG = "ThemeOverlayApplier";
59     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
60 
61     @VisibleForTesting
62     static final String ANDROID_PACKAGE = "android";
63     @VisibleForTesting
64     static final String SETTINGS_PACKAGE = "com.android.settings";
65     @VisibleForTesting
66     static final String SYSUI_PACKAGE = "com.android.systemui";
67 
68     static final String OVERLAY_CATEGORY_ACCENT_COLOR =
69             "android.theme.customization.accent_color";
70     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
71             "android.theme.customization.system_palette";
72     static final String OVERLAY_CATEGORY_THEME_STYLE =
73             "android.theme.customization.theme_style";
74 
75     static final String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source";
76 
77     static final String OVERLAY_COLOR_INDEX = "android.theme.customization.color_index";
78 
79     static final String OVERLAY_COLOR_BOTH = "android.theme.customization.color_both";
80 
81     static final String COLOR_SOURCE_PRESET = "preset";
82 
83     static final String COLOR_SOURCE_HOME = "home_wallpaper";
84 
85     static final String COLOR_SOURCE_LOCK = "lock_wallpaper";
86 
87     static final String TIMESTAMP_FIELD = "_applied_timestamp";
88 
89     @VisibleForTesting
90     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
91     @VisibleForTesting
92     static final String OVERLAY_CATEGORY_SHAPE =
93             "android.theme.customization.adaptive_icon_shape";
94     @VisibleForTesting
95     static final String OVERLAY_CATEGORY_ICON_ANDROID =
96             "android.theme.customization.icon_pack.android";
97     @VisibleForTesting
98     static final String OVERLAY_CATEGORY_ICON_SYSUI =
99             "android.theme.customization.icon_pack.systemui";
100     @VisibleForTesting
101     static final String OVERLAY_CATEGORY_ICON_SETTINGS =
102             "android.theme.customization.icon_pack.settings";
103     @VisibleForTesting
104     static final String OVERLAY_CATEGORY_ICON_LAUNCHER =
105             "android.theme.customization.icon_pack.launcher";
106     @VisibleForTesting
107     static final String OVERLAY_CATEGORY_ICON_THEME_PICKER =
108             "android.theme.customization.icon_pack.themepicker";
109 
110     /*
111      * All theme customization categories used by the system, in order that they should be applied,
112      * starts with launcher and grouped by target package.
113      */
114     static final List<String> THEME_CATEGORIES = Lists.newArrayList(
115             OVERLAY_CATEGORY_SYSTEM_PALETTE,
116             OVERLAY_CATEGORY_ICON_LAUNCHER,
117             OVERLAY_CATEGORY_SHAPE,
118             OVERLAY_CATEGORY_FONT,
119             OVERLAY_CATEGORY_ACCENT_COLOR,
120             OVERLAY_CATEGORY_ICON_ANDROID,
121             OVERLAY_CATEGORY_ICON_SYSUI,
122             OVERLAY_CATEGORY_ICON_SETTINGS,
123             OVERLAY_CATEGORY_ICON_THEME_PICKER);
124 
125     /* Categories that need to be applied to the current user as well as the system user. */
126     @VisibleForTesting
127     static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
128             OVERLAY_CATEGORY_SYSTEM_PALETTE,
129             OVERLAY_CATEGORY_ACCENT_COLOR,
130             OVERLAY_CATEGORY_FONT,
131             OVERLAY_CATEGORY_SHAPE,
132             OVERLAY_CATEGORY_ICON_ANDROID,
133             OVERLAY_CATEGORY_ICON_SYSUI);
134 
135     /* Allowed overlay categories for each target package. */
136     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
137     /* Target package for each overlay category. */
138     private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
139     private final OverlayManager mOverlayManager;
140     private final Executor mBgExecutor;
141     private final String mLauncherPackage;
142     private final String mThemePickerPackage;
143 
144     @Inject
ThemeOverlayApplier(OverlayManager overlayManager, @Background Executor bgExecutor, @Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage, @Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage, DumpManager dumpManager)145     public ThemeOverlayApplier(OverlayManager overlayManager,
146             @Background Executor bgExecutor,
147             @Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage,
148             @Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage,
149             DumpManager dumpManager) {
150         mOverlayManager = overlayManager;
151         mBgExecutor = bgExecutor;
152         mLauncherPackage = launcherPackage;
153         mThemePickerPackage = themePickerPackage;
154         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
155                 OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
156                 OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
157                 OVERLAY_CATEGORY_ICON_ANDROID));
158         mTargetPackageToCategories.put(SYSUI_PACKAGE,
159                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
160         mTargetPackageToCategories.put(SETTINGS_PACKAGE,
161                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS));
162         mTargetPackageToCategories.put(mLauncherPackage,
163                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER));
164         mTargetPackageToCategories.put(mThemePickerPackage,
165                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER));
166         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE);
167         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE);
168         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE);
169         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE);
170         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE);
171         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE);
172         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
173         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
174 
175         dumpManager.registerDumpable(TAG, this);
176     }
177 
178     /**
179      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
180      * affect sysui will also be applied to the system user.
181      */
applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, int currentUser, Set<UserHandle> managedProfiles)182     public void applyCurrentUserOverlays(
183             Map<String, OverlayIdentifier> categoryToPackage,
184             FabricatedOverlay[] pendingCreation,
185             int currentUser,
186             Set<UserHandle> managedProfiles) {
187         mBgExecutor.execute(() -> {
188 
189             // Disable all overlays that have not been specified in the user setting.
190             final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
191             final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream()
192                     .map(category -> mCategoryToTargetPackage.get(category))
193                     .collect(Collectors.toSet());
194             final List<OverlayInfo> overlays = new ArrayList<>();
195             targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
196                     .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
197             final List<Pair<String, String>> overlaysToDisable = overlays.stream()
198                     .filter(o ->
199                             mTargetPackageToCategories.get(o.targetPackageName).contains(
200                                     o.category))
201                     .filter(o -> overlayCategoriesToDisable.contains(o.category))
202                     .filter(o -> !categoryToPackage.containsValue(
203                             new OverlayIdentifier(o.packageName)))
204                     .filter(o -> o.isEnabled())
205                     .map(o -> new Pair<>(o.category, o.packageName))
206                     .collect(Collectors.toList());
207 
208             OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
209             HashSet<OverlayIdentifier> identifiersPending = new HashSet<>();
210             if (pendingCreation != null) {
211                 for (FabricatedOverlay overlay : pendingCreation) {
212                     identifiersPending.add(overlay.getIdentifier());
213                     transaction.registerFabricatedOverlay(overlay);
214                 }
215             }
216 
217             for (Pair<String, String> packageToDisable : overlaysToDisable) {
218                 OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
219                 setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser,
220                         managedProfiles, false, identifiersPending.contains(overlayInfo));
221             }
222 
223             for (String category : THEME_CATEGORIES) {
224                 if (categoryToPackage.containsKey(category)) {
225                     OverlayIdentifier overlayInfo = categoryToPackage.get(category);
226                     setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles,
227                             true, identifiersPending.contains(overlayInfo));
228                 }
229             }
230 
231             try {
232                 mOverlayManager.commit(transaction.build());
233             } catch (SecurityException | IllegalStateException e) {
234                 Log.e(TAG, "setEnabled failed", e);
235             }
236         });
237     }
238 
239     @VisibleForTesting
getTransactionBuilder()240     protected OverlayManagerTransaction.Builder getTransactionBuilder() {
241         return new OverlayManagerTransaction.Builder();
242     }
243 
244     @AnyThread
setEnabled(OverlayManagerTransaction.Builder transaction, OverlayIdentifier identifier, String category, int currentUser, Set<UserHandle> managedProfiles, boolean enabled, boolean pendingCreation)245     private void setEnabled(OverlayManagerTransaction.Builder transaction,
246             OverlayIdentifier identifier, String category, int currentUser,
247             Set<UserHandle> managedProfiles, boolean enabled, boolean pendingCreation) {
248         if (DEBUG) {
249             Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
250                     + category + ": " + enabled);
251         }
252 
253         OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier,
254                 UserHandle.of(currentUser));
255         if (overlayInfo == null && !pendingCreation) {
256             Log.i(TAG, "Won't enable " + identifier + ", it doesn't exist for user"
257                     + currentUser);
258             return;
259         }
260 
261         transaction.setEnabled(identifier, enabled, currentUser);
262         if (currentUser != UserHandle.SYSTEM.getIdentifier()
263                 && SYSTEM_USER_CATEGORIES.contains(category)) {
264             transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
265         }
266 
267         // Do not apply Launcher or Theme picker overlays to managed users. Apps are not
268         // installed in there.
269         overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM);
270         if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage)
271                 || overlayInfo.targetPackageName.equals(mThemePickerPackage)) {
272             return;
273         }
274 
275         for (UserHandle userHandle : managedProfiles) {
276             transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
277         }
278     }
279 
280     /**
281      * @inherit
282      */
283     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)284     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
285         pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
286         pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
287     }
288 }
289