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