blob: c3b4fbe9a13dd995e954b6ef65dad8385493348c [file] [log] [blame]
/*
* 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);
}
}