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.content.om.OverlayInfo; 19 import android.content.om.OverlayManager; 20 import android.os.UserHandle; 21 import android.util.ArrayMap; 22 import android.util.Log; 23 24 import androidx.annotation.VisibleForTesting; 25 26 import com.google.android.collect.Lists; 27 import com.google.android.collect.Sets; 28 29 import java.util.ArrayList; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.concurrent.Executor; 35 import java.util.stream.Collectors; 36 37 class ThemeOverlayManager { 38 private static final String TAG = "ThemeOverlayManager"; 39 private static final boolean DEBUG = false; 40 41 @VisibleForTesting 42 static final String ANDROID_PACKAGE = "android"; 43 @VisibleForTesting 44 static final String SETTINGS_PACKAGE = "com.android.settings"; 45 @VisibleForTesting 46 static final String SYSUI_PACKAGE = "com.android.systemui"; 47 48 @VisibleForTesting 49 static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color"; 50 @VisibleForTesting 51 static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; 52 @VisibleForTesting 53 static final String OVERLAY_CATEGORY_SHAPE = 54 "android.theme.customization.adaptive_icon_shape"; 55 @VisibleForTesting 56 static final String OVERLAY_CATEGORY_ICON_ANDROID = 57 "android.theme.customization.icon_pack.android"; 58 @VisibleForTesting 59 static final String OVERLAY_CATEGORY_ICON_SYSUI = 60 "android.theme.customization.icon_pack.systemui"; 61 @VisibleForTesting 62 static final String OVERLAY_CATEGORY_ICON_SETTINGS = 63 "android.theme.customization.icon_pack.settings"; 64 @VisibleForTesting 65 static final String OVERLAY_CATEGORY_ICON_LAUNCHER = 66 "android.theme.customization.icon_pack.launcher"; 67 @VisibleForTesting 68 static final String OVERLAY_CATEGORY_ICON_THEME_PICKER = 69 "android.theme.customization.icon_pack.themepicker"; 70 71 /* 72 * All theme customization categories used by the system, in order that they should be applied, 73 * starts with launcher and grouped by target package. 74 */ 75 static final List<String> THEME_CATEGORIES = Lists.newArrayList( 76 OVERLAY_CATEGORY_ICON_LAUNCHER, 77 OVERLAY_CATEGORY_SHAPE, 78 OVERLAY_CATEGORY_FONT, 79 OVERLAY_CATEGORY_COLOR, 80 OVERLAY_CATEGORY_ICON_ANDROID, 81 OVERLAY_CATEGORY_ICON_SYSUI, 82 OVERLAY_CATEGORY_ICON_SETTINGS, 83 OVERLAY_CATEGORY_ICON_THEME_PICKER); 84 85 /* Categories that need to applied to the current user as well as the system user. */ 86 @VisibleForTesting 87 static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet( 88 OVERLAY_CATEGORY_COLOR, 89 OVERLAY_CATEGORY_FONT, 90 OVERLAY_CATEGORY_SHAPE, 91 OVERLAY_CATEGORY_ICON_ANDROID, 92 OVERLAY_CATEGORY_ICON_SYSUI); 93 94 /* Allowed overlay categories for each target package. */ 95 private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>(); 96 /* Target package for each overlay category. */ 97 private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); 98 private final OverlayManager mOverlayManager; 99 private final Executor mExecutor; 100 private final String mLauncherPackage; 101 private final String mThemePickerPackage; 102 ThemeOverlayManager(OverlayManager overlayManager, Executor executor, String launcherPackage, String themePickerPackage)103 ThemeOverlayManager(OverlayManager overlayManager, Executor executor, 104 String launcherPackage, String themePickerPackage) { 105 mOverlayManager = overlayManager; 106 mExecutor = executor; 107 mLauncherPackage = launcherPackage; 108 mThemePickerPackage = themePickerPackage; 109 mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( 110 OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT, 111 OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID)); 112 mTargetPackageToCategories.put(SYSUI_PACKAGE, 113 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI)); 114 mTargetPackageToCategories.put(SETTINGS_PACKAGE, 115 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS)); 116 mTargetPackageToCategories.put(mLauncherPackage, 117 Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER)); 118 mTargetPackageToCategories.put(mThemePickerPackage, 119 Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER)); 120 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_COLOR, ANDROID_PACKAGE); 121 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE); 122 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE); 123 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE); 124 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE); 125 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE); 126 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); 127 mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage); 128 } 129 130 /** 131 * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that 132 * affect sysui will also be applied to the system user. 133 */ applyCurrentUserOverlays( Map<String, String> categoryToPackage, Set<UserHandle> userHandles)134 void applyCurrentUserOverlays( 135 Map<String, String> categoryToPackage, Set<UserHandle> userHandles) { 136 // Disable all overlays that have not been specified in the user setting. 137 final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); 138 overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); 139 final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() 140 .map(category -> mCategoryToTargetPackage.get(category)) 141 .collect(Collectors.toSet()); 142 final List<OverlayInfo> overlays = new ArrayList<>(); 143 targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager 144 .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM))); 145 final Map<String, String> overlaysToDisable = overlays.stream() 146 .filter(o -> 147 mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) 148 .filter(o -> overlayCategoriesToDisable.contains(o.category)) 149 .filter(o -> o.isEnabled()) 150 .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName)); 151 152 // Toggle overlays in the order of THEME_CATEGORIES. 153 for (String category : THEME_CATEGORIES) { 154 if (categoryToPackage.containsKey(category)) { 155 setEnabled(categoryToPackage.get(category), category, userHandles, true); 156 } else if (overlaysToDisable.containsKey(category)) { 157 setEnabled(overlaysToDisable.get(category), category, userHandles, false); 158 } 159 } 160 } 161 setEnabled( String packageName, String category, Set<UserHandle> handles, boolean enabled)162 private void setEnabled( 163 String packageName, String category, Set<UserHandle> handles, boolean enabled) { 164 for (UserHandle userHandle : handles) { 165 setEnabledAsync(packageName, userHandle, enabled); 166 } 167 if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { 168 setEnabledAsync(packageName, UserHandle.SYSTEM, enabled); 169 } 170 } 171 setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled)172 private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) { 173 mExecutor.execute(() -> { 174 if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled)); 175 try { 176 if (enabled) { 177 mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle); 178 } else { 179 mOverlayManager.setEnabled(pkg, false, userHandle); 180 } 181 } catch (SecurityException | IllegalStateException e) { 182 Log.e(TAG, 183 String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e); 184 } 185 }); 186 } 187 } 188