Custom Theme 2/n: infrastructure for loading options
Adds support to read components' options from overlays.
Only FontOptions are implemented in this CL, the rest are coming in
subsequent CLs.
Bug: 124796742
Change-Id: If70b86f85d360b1ad7fb4c8cd3f1e45c94ca1856
diff --git a/src/com/android/customization/model/ResourceConstants.java b/src/com/android/customization/model/ResourceConstants.java
index 2822f4a..ad16881 100644
--- a/src/com/android/customization/model/ResourceConstants.java
+++ b/src/com/android/customization/model/ResourceConstants.java
@@ -37,6 +37,8 @@
*/
String SYSUI_PACKAGE = "com.android.systemui";
+ String [] DEFAULT_TARGET_PACKAGES = {ANDROID_PACKAGE, SETTINGS_PACKAGE, SYSUI_PACKAGE};
+
/**
* Name of the system resource for icon mask
*/
@@ -56,4 +58,6 @@
* Secure Setting used to store the currently set theme.
*/
String THEME_SETTING = Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES;
+ String CONFIG_BODY_FONT_FAMILY = "config_bodyFontFamily";
+ String CONFIG_HEADLINE_FONT_FAMILY = "config_headlineFontFamily";
}
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 3dcb90e..a48c67b 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -35,8 +35,10 @@
import androidx.annotation.Nullable;
import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.ResourceConstants;
import com.android.customization.model.ResourcesApkProvider;
import com.android.customization.model.theme.ThemeBundle.Builder;
+import com.android.customization.model.theme.custom.CustomTheme;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.ResourceAsset;
@@ -75,8 +77,6 @@
private static final String ACCENT_COLOR_LIGHT_NAME = "accent_device_default_light";
private static final String ACCENT_COLOR_DARK_NAME = "accent_device_default_dark";
- private static final String CONFIG_BODY_FONT_FAMILY = "config_bodyFontFamily";
- private static final String CONFIG_HEADLINE_FONT_FAMILY = "config_headlineFontFamily";
private static final String[] SYSUI_ICONS_FOR_PREVIEW = {
"ic_qs_bluetooth_on",
"ic_dnd",
@@ -133,9 +133,11 @@
if (!TextUtils.isEmpty(fontOverlayPackage)) {
builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
fontOverlayPackage)
- .setBodyFontFamily(loadTypeface(CONFIG_BODY_FONT_FAMILY,
+ .setBodyFontFamily(loadTypeface(
+ ResourceConstants.CONFIG_BODY_FONT_FAMILY,
fontOverlayPackage))
- .setHeadlineFontFamily(loadTypeface(CONFIG_HEADLINE_FONT_FAMILY,
+ .setHeadlineFontFamily(loadTypeface(
+ ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
fontOverlayPackage));
}
@@ -270,15 +272,17 @@
try {
builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage), fontOverlayPackage)
- .setBodyFontFamily(loadTypeface(CONFIG_BODY_FONT_FAMILY,
+ .setBodyFontFamily(loadTypeface(ResourceConstants.CONFIG_BODY_FONT_FAMILY,
fontOverlayPackage))
- .setHeadlineFontFamily(loadTypeface(CONFIG_HEADLINE_FONT_FAMILY,
+ .setHeadlineFontFamily(loadTypeface(
+ ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
fontOverlayPackage));
} catch (NameNotFoundException | NotFoundException e) {
Log.d(TAG, "Didn't find font overlay for default theme, will use system default", e);
String headlineFontFamily = system.getString(system.getIdentifier(
- CONFIG_HEADLINE_FONT_FAMILY,"string", ANDROID_PACKAGE));
- String bodyFontFamily = system.getString(system.getIdentifier(CONFIG_BODY_FONT_FAMILY,
+ ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,"string", ANDROID_PACKAGE));
+ String bodyFontFamily = system.getString(system.getIdentifier(
+ ResourceConstants.CONFIG_BODY_FONT_FAMILY,
"string", ANDROID_PACKAGE));
builder.setHeadlineFontFamily(Typeface.create(headlineFontFamily, Typeface.NORMAL))
.setBodyFontFamily(Typeface.create(bodyFontFamily, Typeface.NORMAL));
diff --git a/src/com/android/customization/model/theme/OverlayManagerCompat.java b/src/com/android/customization/model/theme/OverlayManagerCompat.java
index a32f120..c2e4c8e 100644
--- a/src/com/android/customization/model/theme/OverlayManagerCompat.java
+++ b/src/com/android/customization/model/theme/OverlayManagerCompat.java
@@ -22,6 +22,10 @@
import androidx.annotation.Nullable;
+import com.android.customization.model.ResourceConstants;
+
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -32,6 +36,7 @@
*/
public class OverlayManagerCompat {
private final OverlayManager mOverlayManager;
+ private Map<Integer, Map<String, List<OverlayInfo>>> mOverlayByUser;
public OverlayManagerCompat(Context context) {
mOverlayManager = context.getSystemService(OverlayManager.class);
@@ -59,6 +64,7 @@
*/
@Nullable
public String getEnabledPackageName(String targetPackageName, String category) {
+ // Can't use mOverlayByUser map as the enabled state might change
List<OverlayInfo> overlayInfos = getOverlayInfosForTarget(targetPackageName,
UserHandle.myUserId());
for (OverlayInfo overlayInfo : overlayInfos) {
@@ -81,13 +87,42 @@
return overlays;
}
+ public List<String> getOverlayPackagesForCategory(String category, int userId,
+ String... targetPackages) {
+ List<String> overlays = new ArrayList<>();
+ ensureCategoryMapForUser(userId);
+ for (String target : targetPackages) {
+ for (OverlayInfo info
+ : mOverlayByUser.get(userId).getOrDefault(target, Collections.emptyList())) {
+ if (category.equals(info.category)) {
+ overlays.add(info.packageName);
+ }
+ }
+ }
+ return overlays;
+ }
+
+ private void ensureCategoryMapForUser(int userId) {
+ if (mOverlayByUser == null) {
+ mOverlayByUser = new HashMap<>();
+ }
+ if (!mOverlayByUser.containsKey(userId)) {
+ Map<String, List<OverlayInfo>> overlaysByTarget = new HashMap<>();
+ for (String target : ResourceConstants.DEFAULT_TARGET_PACKAGES) {
+ overlaysByTarget.put(target, getOverlayInfosForTarget(target, userId));
+ }
+ mOverlayByUser.put(userId, overlaysByTarget);
+ }
+ }
+
+
private List<OverlayInfo> getOverlayInfosForTarget(String targetPackageName, int userId) {
return mOverlayManager.getOverlayInfosForTarget(targetPackageName, userId);
}
private void addAllEnabledOverlaysForTarget(Map<String, String> overlays, String target) {
- for (OverlayInfo overlayInfo : getOverlayInfosForTarget(target,
- UserHandle.myUserId())) {
+ // Can't use mOverlayByUser map as the enabled state might change
+ for (OverlayInfo overlayInfo : getOverlayInfosForTarget(target, UserHandle.myUserId())) {
if (overlayInfo.isEnabled()) {
overlays.put(overlayInfo.category, overlayInfo.packageName);
}
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index a37893b..91192dc 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -16,6 +16,7 @@
package com.android.customization.model.theme;
import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
@@ -155,8 +156,8 @@
public Map<String, String> getCurrentOverlays() {
if (mCurrentOverlays == null) {
- mCurrentOverlays = mOverlayManagerCompat.getEnabledOverlaysForTargets(ANDROID_PACKAGE,
- SYSUI_PACKAGE, SETTINGS_PACKAGE);
+ mCurrentOverlays = mOverlayManagerCompat.getEnabledOverlaysForTargets(
+ DEFAULT_TARGET_PACKAGES);
mCurrentOverlays.entrySet().removeIf(
categoryAndPackage -> !THEME_CATEGORIES.contains(categoryAndPackage.getKey()));
}
@@ -164,6 +165,7 @@
}
public String getStoredOverlays() {
- return Settings.Secure.getString(mActivity.getContentResolver(), ResourceConstants.THEME_SETTING);
+ return Settings.Secure.getString(mActivity.getContentResolver(),
+ ResourceConstants.THEME_SETTING);
}
}
diff --git a/src/com/android/customization/model/theme/CustomTheme.java b/src/com/android/customization/model/theme/custom/CustomTheme.java
similarity index 92%
rename from src/com/android/customization/model/theme/CustomTheme.java
rename to src/com/android/customization/model/theme/custom/CustomTheme.java
index 289e3e4..982378f 100644
--- a/src/com/android/customization/model/theme/CustomTheme.java
+++ b/src/com/android/customization/model/theme/custom/CustomTheme.java
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.customization.model.theme;
+package com.android.customization.model.theme.custom;
import android.view.View;
+import com.android.customization.model.theme.ThemeBundle;
import com.android.wallpaper.R;
import java.util.Map;
diff --git a/src/com/android/customization/model/theme/custom/FontOptionsProvider.java b/src/com/android/customization/model/theme/custom/FontOptionsProvider.java
new file mode 100644
index 0000000..ef423e7
--- /dev/null
+++ b/src/com/android/customization/model/theme/custom/FontOptionsProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.customization.model.theme.custom;
+
+import static com.android.customization.model.ResourceConstants.CONFIG_BODY_FONT_FAMILY;
+import static com.android.customization.model.ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Typeface;
+import android.util.Log;
+
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption;
+
+/**
+ * Implementation of {@link ThemeComponentOptionProvider} that reads {@link FontOption}s from
+ * font overlays.
+ */
+public class FontOptionsProvider extends ThemeComponentOptionProvider<FontOption> {
+
+ private static final String TAG = "FontOptionsProvider";
+
+ public FontOptionsProvider(Context context, OverlayManagerCompat manager) {
+ super(context, manager, OVERLAY_CATEGORY_FONT);
+ }
+
+ @Override
+ protected void loadOptions() {
+ for (String overlayPackage : mOverlayPackages) {
+ try {
+ Resources overlayRes = getOverlayResources(overlayPackage);
+ Typeface headlineFont = Typeface.create(
+ getFontFamily(overlayPackage, overlayRes, CONFIG_HEADLINE_FONT_FAMILY),
+ Typeface.NORMAL);
+ Typeface bodyFont = Typeface.create(
+ getFontFamily(overlayPackage, overlayRes, CONFIG_BODY_FONT_FAMILY),
+ Typeface.NORMAL);
+ PackageManager pm = mContext.getPackageManager();
+ String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString();
+ mOptions.add(new FontOption(overlayPackage, label, headlineFont, bodyFont));
+ } catch (NameNotFoundException | NotFoundException e) {
+ Log.w(TAG, String.format("Couldn't load font overlay %s, will skip it",
+ overlayPackage), e);
+ }
+ }
+ }
+
+ private String getFontFamily(String overlayPackage, Resources overlayRes, String configName) {
+ return overlayRes.getString(overlayRes.getIdentifier(configName, "string", overlayPackage));
+ }
+}
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
new file mode 100644
index 0000000..71cb31a
--- /dev/null
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -0,0 +1,170 @@
+/*
+ * 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.customization.model.theme.custom;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+
+/**
+ * Represents an option of a component of a custom Theme (for example, a possible color, or font,
+ * shape, etc).
+ * Extending classes correspond to each component's options and provide the structure to bind
+ * preview and thumbnails.
+ */
+public abstract class ThemeComponentOption implements CustomizationOption<ThemeComponentOption> {
+
+ protected final String mOverlayPackageName;
+
+ ThemeComponentOption(String packageName) {
+ mOverlayPackageName = packageName;
+ }
+
+ public String getOverlayPackageName() {
+ return mOverlayPackageName;
+ }
+
+ @Override
+ public String getTitle() {
+ return null;
+ }
+
+ public abstract void bindPreview(ViewGroup container);
+
+ public static class FontOption extends ThemeComponentOption {
+
+ private final String mLabel;
+ private final Typeface mHeadlineFont;
+ private final Typeface mBodyFont;
+
+ public FontOption(String packageName, String label, Typeface headlineFont,
+ Typeface bodyFont) {
+ super(packageName);
+ mLabel = label;
+ mHeadlineFont = headlineFont;
+ mBodyFont = bodyFont;
+ }
+
+ @Override
+ public String getTitle() {
+ return mLabel;
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+
+ }
+
+ @Override
+ public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
+ return false;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return 0;
+ }
+
+ @Override
+ public void bindPreview(ViewGroup container) {
+
+ }
+ }
+
+ public static class IconOption extends ThemeComponentOption {
+
+ IconOption(String packageName) {
+ super(packageName);
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+
+ }
+
+ @Override
+ public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
+ return false;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return 0;
+ }
+
+ @Override
+ public void bindPreview(ViewGroup container) {
+
+ }
+ }
+
+ public static class ColorOption extends ThemeComponentOption {
+
+ ColorOption(String packageName) {
+ super(packageName);
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+
+ }
+
+ @Override
+ public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
+ return false;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return 0;
+ }
+
+ @Override
+ public void bindPreview(ViewGroup container) {
+
+ }
+ }
+
+ public static class ShapeOption extends ThemeComponentOption {
+
+ ShapeOption(String packageName) {
+ super(packageName);
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+
+ }
+
+ @Override
+ public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
+ return false;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return 0;
+ }
+
+ @Override
+ public void bindPreview(ViewGroup container) {
+
+ }
+ }
+}
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java b/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java
new file mode 100644
index 0000000..316ee99
--- /dev/null
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java
@@ -0,0 +1,79 @@
+/*
+ * 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.customization.model.theme.custom;
+
+import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.UserHandle;
+
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class used to retrieve Custom Theme Component options (eg, different fonts)
+ * from the system.
+ */
+public abstract class ThemeComponentOptionProvider<T extends ThemeComponentOption> {
+
+ protected final Context mContext;
+ protected final List<String> mOverlayPackages;
+ protected List<T> mOptions;
+
+ public ThemeComponentOptionProvider(Context context, OverlayManagerCompat manager,
+ String... categories) {
+ mContext = context;
+ mOverlayPackages = new ArrayList<>();
+ for (String category : categories) {
+ mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(category,
+ UserHandle.myUserId(), DEFAULT_TARGET_PACKAGES));
+ }
+ }
+
+ /**
+ * Returns whether there are options for this component available in the current setup.
+ */
+ public boolean isAvailable() {
+ return !mOverlayPackages.isEmpty();
+ }
+
+ /**
+ * Retrieve the available options for this component.
+ * @param callback called when the themes have been retrieved (or immediately if cached)
+ * @param reload whether to reload themes if they're cached.
+ */
+ public void fetch(OptionsFetchedListener<T> callback, boolean reload) {
+ if (mOptions == null || reload) {
+ mOptions = new ArrayList<>();
+ loadOptions();
+ }
+
+ if(callback != null) {
+ callback.onOptionsLoaded(mOptions);
+ }
+ }
+
+ protected abstract void loadOptions();
+
+ protected Resources getOverlayResources(String overlayPackage) throws NameNotFoundException {
+ return mContext.getPackageManager().getResourcesForApplication(overlayPackage);
+ }
+}
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index 669f21c..4487247 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -44,7 +44,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.customization.model.CustomizationManager.Callback;
-import com.android.customization.model.theme.CustomTheme;
+import com.android.customization.model.theme.custom.CustomTheme;
import com.android.customization.model.theme.ThemeBundle;
import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
import com.android.customization.model.theme.ThemeManager;