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