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