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