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