| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.systemui.theme; |
| |
| import android.annotation.AnyThread; |
| import android.content.om.FabricatedOverlay; |
| import android.content.om.OverlayIdentifier; |
| import android.content.om.OverlayInfo; |
| import android.content.om.OverlayManager; |
| import android.content.om.OverlayManagerTransaction; |
| import android.os.UserHandle; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.systemui.Dumpable; |
| import com.android.systemui.dagger.SysUISingleton; |
| import com.android.systemui.dump.DumpManager; |
| |
| import com.google.android.collect.Lists; |
| import com.google.android.collect.Sets; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Responsible for orchestrating overlays, based on user preferences and other inputs from |
| * {@link ThemeOverlayController}. |
| */ |
| @SysUISingleton |
| public class ThemeOverlayApplier implements Dumpable { |
| private static final String TAG = "ThemeOverlayApplier"; |
| private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| @VisibleForTesting |
| static final String ANDROID_PACKAGE = "android"; |
| @VisibleForTesting |
| static final String SETTINGS_PACKAGE = "com.android.settings"; |
| @VisibleForTesting |
| static final String SYSUI_PACKAGE = "com.android.systemui"; |
| |
| static final String OVERLAY_CATEGORY_ACCENT_COLOR = |
| "android.theme.customization.accent_color"; |
| static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = |
| "android.theme.customization.system_palette"; |
| |
| static final String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source"; |
| |
| static final String OVERLAY_COLOR_INDEX = "android.theme.customization.color_index"; |
| |
| static final String OVERLAY_COLOR_BOTH = "android.theme.customization.color_both"; |
| |
| static final String COLOR_SOURCE_PRESET = "preset"; |
| |
| static final String COLOR_SOURCE_HOME = "home_wallpaper"; |
| |
| static final String COLOR_SOURCE_LOCK = "lock_wallpaper"; |
| |
| static final String TIMESTAMP_FIELD = "_applied_timestamp"; |
| |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_SHAPE = |
| "android.theme.customization.adaptive_icon_shape"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_ICON_ANDROID = |
| "android.theme.customization.icon_pack.android"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_ICON_SYSUI = |
| "android.theme.customization.icon_pack.systemui"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_ICON_SETTINGS = |
| "android.theme.customization.icon_pack.settings"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_ICON_LAUNCHER = |
| "android.theme.customization.icon_pack.launcher"; |
| @VisibleForTesting |
| static final String OVERLAY_CATEGORY_ICON_THEME_PICKER = |
| "android.theme.customization.icon_pack.themepicker"; |
| |
| /* |
| * All theme customization categories used by the system, in order that they should be applied, |
| * starts with launcher and grouped by target package. |
| */ |
| static final List<String> THEME_CATEGORIES = Lists.newArrayList( |
| OVERLAY_CATEGORY_SYSTEM_PALETTE, |
| OVERLAY_CATEGORY_ICON_LAUNCHER, |
| OVERLAY_CATEGORY_SHAPE, |
| OVERLAY_CATEGORY_FONT, |
| OVERLAY_CATEGORY_ACCENT_COLOR, |
| OVERLAY_CATEGORY_ICON_ANDROID, |
| OVERLAY_CATEGORY_ICON_SYSUI, |
| OVERLAY_CATEGORY_ICON_SETTINGS, |
| OVERLAY_CATEGORY_ICON_THEME_PICKER); |
| |
| /* Categories that need to be applied to the current user as well as the system user. */ |
| @VisibleForTesting |
| static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet( |
| OVERLAY_CATEGORY_SYSTEM_PALETTE, |
| OVERLAY_CATEGORY_ACCENT_COLOR, |
| OVERLAY_CATEGORY_FONT, |
| OVERLAY_CATEGORY_SHAPE, |
| OVERLAY_CATEGORY_ICON_ANDROID, |
| OVERLAY_CATEGORY_ICON_SYSUI); |
| |
| /* Allowed overlay categories for each target package. */ |
| private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>(); |
| /* Target package for each overlay category. */ |
| private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); |
| private final OverlayManager mOverlayManager; |
| private final Executor mExecutor; |
| private final String mLauncherPackage; |
| private final String mThemePickerPackage; |
| |
| public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor, |
| String launcherPackage, String themePickerPackage, DumpManager dumpManager) { |
| mOverlayManager = overlayManager; |
| mExecutor = executor; |
| mLauncherPackage = launcherPackage; |
| mThemePickerPackage = themePickerPackage; |
| mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( |
| OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR, |
| OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, |
| OVERLAY_CATEGORY_ICON_ANDROID)); |
| mTargetPackageToCategories.put(SYSUI_PACKAGE, |
| Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI)); |
| mTargetPackageToCategories.put(SETTINGS_PACKAGE, |
| Sets.newHashSet(OVERLAY_CATEGORY_ICON_SETTINGS)); |
| mTargetPackageToCategories.put(mLauncherPackage, |
| Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER)); |
| mTargetPackageToCategories.put(mThemePickerPackage, |
| Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER)); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SYSUI, SYSUI_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage); |
| mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage); |
| |
| dumpManager.registerDumpable(TAG, this); |
| } |
| |
| /** |
| * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that |
| * affect sysui will also be applied to the system user. |
| */ |
| void applyCurrentUserOverlays( |
| Map<String, OverlayIdentifier> categoryToPackage, |
| FabricatedOverlay[] pendingCreation, |
| int currentUser, |
| Set<UserHandle> managedProfiles) { |
| mExecutor.execute(() -> { |
| |
| // Disable all overlays that have not been specified in the user setting. |
| final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); |
| final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() |
| .map(category -> mCategoryToTargetPackage.get(category)) |
| .collect(Collectors.toSet()); |
| final List<OverlayInfo> overlays = new ArrayList<>(); |
| targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager |
| .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM))); |
| final List<Pair<String, String>> overlaysToDisable = overlays.stream() |
| .filter(o -> |
| mTargetPackageToCategories.get(o.targetPackageName).contains( |
| o.category)) |
| .filter(o -> overlayCategoriesToDisable.contains(o.category)) |
| .filter(o -> !categoryToPackage.containsValue( |
| new OverlayIdentifier(o.packageName))) |
| .filter(o -> o.isEnabled()) |
| .map(o -> new Pair<>(o.category, o.packageName)) |
| .collect(Collectors.toList()); |
| |
| OverlayManagerTransaction.Builder transaction = getTransactionBuilder(); |
| HashSet<OverlayIdentifier> identifiersPending = new HashSet<>(); |
| if (pendingCreation != null) { |
| for (FabricatedOverlay overlay : pendingCreation) { |
| identifiersPending.add(overlay.getIdentifier()); |
| transaction.registerFabricatedOverlay(overlay); |
| } |
| } |
| |
| for (Pair<String, String> packageToDisable : overlaysToDisable) { |
| OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); |
| setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser, |
| managedProfiles, false, identifiersPending.contains(overlayInfo)); |
| } |
| |
| for (String category : THEME_CATEGORIES) { |
| if (categoryToPackage.containsKey(category)) { |
| OverlayIdentifier overlayInfo = categoryToPackage.get(category); |
| setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, |
| true, identifiersPending.contains(overlayInfo)); |
| } |
| } |
| |
| try { |
| mOverlayManager.commit(transaction.build()); |
| } catch (SecurityException | IllegalStateException e) { |
| Log.e(TAG, "setEnabled failed", e); |
| } |
| }); |
| } |
| |
| @VisibleForTesting |
| protected OverlayManagerTransaction.Builder getTransactionBuilder() { |
| return new OverlayManagerTransaction.Builder(); |
| } |
| |
| @AnyThread |
| private void setEnabled(OverlayManagerTransaction.Builder transaction, |
| OverlayIdentifier identifier, String category, int currentUser, |
| Set<UserHandle> managedProfiles, boolean enabled, boolean pendingCreation) { |
| if (DEBUG) { |
| Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " |
| + category + ": " + enabled); |
| } |
| |
| OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, |
| UserHandle.of(currentUser)); |
| if (overlayInfo == null && !pendingCreation) { |
| Log.i(TAG, "Won't enable " + identifier + ", it doesn't exist for user" |
| + currentUser); |
| return; |
| } |
| |
| transaction.setEnabled(identifier, enabled, currentUser); |
| if (currentUser != UserHandle.SYSTEM.getIdentifier() |
| && SYSTEM_USER_CATEGORIES.contains(category)) { |
| transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); |
| } |
| |
| // Do not apply Launcher or Theme picker overlays to managed users. Apps are not |
| // installed in there. |
| overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM); |
| if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage) |
| || overlayInfo.targetPackageName.equals(mThemePickerPackage)) { |
| return; |
| } |
| |
| for (UserHandle userHandle : managedProfiles) { |
| transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); |
| } |
| } |
| |
| /** |
| * @inherit |
| */ |
| @Override |
| public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { |
| pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories); |
| pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage); |
| } |
| } |