Custom Theme 9/n: add shape step screen
Bug: 124796742
Change-Id: Ibd84051452cc1ca8e11dc46ae0189a3f91eb2b7e
diff --git a/res/layout/theme_shape_option.xml b/res/layout/theme_shape_option.xml
new file mode 100644
index 0000000..94e31ea
--- /dev/null
+++ b/res/layout/theme_shape_option.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <FrameLayout
+ android:id="@+id/option_tile"
+ android:layout_width="@dimen/option_tile_width"
+ android:layout_height="@dimen/option_tile_width"
+ android:layout_gravity="center"
+ android:layout_marginTop="16dp"
+ android:padding="2dp">
+ <ImageView
+ android:id="@+id/shape_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3aa48ce..1549395 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -38,4 +38,6 @@
<color name="tile_enabled_icon_color">@color/material_white_100</color>
<color name="tile_disabled_icon_color">#2d2d2d</color>
<color name="tile_disabled_background_color">@color/light_grey</color>
+
+ <color name="shape_option_tile_foreground_color">#f8f9fa</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d76236d..f3ae3b1 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -76,4 +76,7 @@
<dimen name="component_icon_thumb_size">40dp</dimen>
<dimen name="component_color_chip_size">42dp</dimen>
+
+ <dimen name="component_shape_thumb_size">72dp</dimen>
+ <dimen name="component_shape_border_width">3dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6aae5ff..01f57d6 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -122,6 +122,9 @@
<!-- Title of a page allowing the user to choose a color for a custom theme -->
<string name="color_component_title">Choose color</string>
+ <!-- Title of a page allowing the user to choose an icon shape for a custom theme -->
+ <string name="shape_component_title">Choose shape</string>
+
<!-- Title of a set of icons that the user can chose for their custom style (eg, "Icons 2") -->
<string name="icon_component_label">Icons <xliff:g name="component_number" example="1">%1$d</xliff:g></string>
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 439a7d6..6bb4926 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -19,12 +19,14 @@
import static com.android.customization.model.ResourceConstants.ACCENT_COLOR_LIGHT_NAME;
import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW;
import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER;
import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
@@ -70,7 +72,7 @@
private static final String TAG = "DefaultThemeProvider";
// TODO(b/124796742): remove once custom theme picker is ready to merge
- private static final boolean SHOW_CUSTOM_THEME_OPTION = false;
+ private static final boolean SHOW_CUSTOM_THEME_OPTION = true;
private static final String THEMES_ARRAY = "themes";
private static final String TITLE_PREFIX = "theme_title_";
@@ -141,37 +143,13 @@
"string", mStubPackageName)));
String shapeOverlayPackage = getOverlayPackage(SHAPE_PREFIX, themeName);
- if (!TextUtils.isEmpty(shapeOverlayPackage)) {
- builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage),
- shapeOverlayPackage)
- .setShapePath(loadString(CONFIG_ICON_MASK, shapeOverlayPackage));
- } else {
- builder.setShapePath(mContext.getResources().getString(
- Resources.getSystem().getIdentifier(CONFIG_ICON_MASK, "string",
- ANDROID_PACKAGE)));
- }
- for (String packageName : mShapePreviewIconPackages) {
- try {
- builder.addShapePreviewIcon(
- mContext.getPackageManager().getApplicationIcon(packageName));
- } catch (NameNotFoundException e) {
- Log.d(TAG, "Couldn't find app " + packageName
- + ", won't use it for icon shape preview");
- }
- }
+ addShapeOverlay(builder, shapeOverlayPackage);
String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, themeName);
addFontOverlay(builder, fontOverlayPackage);
String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, themeName);
- if (!TextUtils.isEmpty(colorOverlayPackage)) {
- builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage),
- colorOverlayPackage)
- .setColorAccentLight(loadColor(ACCENT_COLOR_LIGHT_NAME,
- colorOverlayPackage))
- .setColorAccentDark(loadColor(ACCENT_COLOR_DARK_NAME,
- colorOverlayPackage));
- }
+ addColorOverlay(builder, colorOverlayPackage);
String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX,
themeName);
@@ -224,6 +202,42 @@
}
}
+ private void addColorOverlay(Builder builder, String colorOverlayPackage)
+ throws NameNotFoundException {
+ if (!TextUtils.isEmpty(colorOverlayPackage)) {
+ builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage),
+ colorOverlayPackage)
+ .setColorAccentLight(loadColor(ACCENT_COLOR_LIGHT_NAME,
+ colorOverlayPackage))
+ .setColorAccentDark(loadColor(ACCENT_COLOR_DARK_NAME,
+ colorOverlayPackage));
+ } else {
+ addSystemDefaultColor(builder);
+ }
+ }
+
+ private void addShapeOverlay(Builder builder, String shapeOverlayPackage)
+ throws NameNotFoundException {
+ if (!TextUtils.isEmpty(shapeOverlayPackage)) {
+ builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage),
+ shapeOverlayPackage)
+ .setShapePath(loadString(CONFIG_ICON_MASK, shapeOverlayPackage));
+ } else {
+ builder.setShapePath(mContext.getResources().getString(
+ Resources.getSystem().getIdentifier(CONFIG_ICON_MASK, "string",
+ ANDROID_PACKAGE)));
+ }
+ for (String packageName : mShapePreviewIconPackages) {
+ try {
+ builder.addShapePreviewIcon(
+ mContext.getPackageManager().getApplicationIcon(packageName));
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Couldn't find app " + packageName
+ + ", won't use it for icon shape preview");
+ }
+ }
+ }
+
private void addNoPreviewIconOverlay(Builder builder, String overlayPackage) {
if (!TextUtils.isEmpty(overlayPackage)) {
builder.addOverlayPackage(getOverlayCategory(overlayPackage),
@@ -461,7 +475,9 @@
}
CustomTheme.Builder builder = new CustomTheme.Builder();
builder.setTitle(mContext.getString(R.string.custom_theme_title));
+ addShapeOverlay(builder, customPackages.get(OVERLAY_CATEGORY_SHAPE));
addFontOverlay(builder, customPackages.get(OVERLAY_CATEGORY_FONT));
+ addColorOverlay(builder, customPackages.get(OVERLAY_CATEGORY_COLOR));
addAndroidIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_ANDROID));
addSysUiIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_SYSUI));
addNoPreviewIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_SETTINGS));
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index 578b2cd..50b70cb 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -40,7 +40,6 @@
import com.android.customization.model.CustomizationManager;
import com.android.customization.model.CustomizationOption;
import com.android.customization.widget.DynamicAdaptiveIconDrawable;
-import com.android.customization.model.ResourceConstants;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.Asset;
import com.android.wallpaper.asset.ResourceAsset;
diff --git a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
new file mode 100644
index 0000000..ccd8679
--- /dev/null
+++ b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
+import static com.android.customization.model.ResourceConstants.PATH_SIZE;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Path;
+import android.graphics.Typeface;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.core.graphics.PathParser;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption;
+import com.android.customization.widget.DynamicAdaptiveIconDrawable;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link ThemeComponentOptionProvider} that reads {@link ShapeOption}s from
+ * icon overlays.
+ */
+public class ShapeOptionsProvider extends ThemeComponentOptionProvider<ShapeOption> {
+
+ private static final String TAG = "FontOptionsProvider";
+ private final String[] mShapePreviewIconPackages;
+ private int mThumbSize;
+
+ public ShapeOptionsProvider(Context context, OverlayManagerCompat manager) {
+ super(context, manager, OVERLAY_CATEGORY_SHAPE);
+ mShapePreviewIconPackages = context.getResources().getStringArray(
+ R.array.icon_shape_preview_packages);
+ mThumbSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.component_shape_thumb_size);
+ }
+
+ @Override
+ protected void loadOptions() {
+ addDefault();
+ for (String overlayPackage : mOverlayPackages) {
+ try {
+ Path path = loadPath(mContext.getPackageManager()
+ .getResourcesForApplication(overlayPackage), overlayPackage);
+ ShapeDrawable shapeDrawable = createShapeDrawable(path);
+ mOptions.add(new ShapeOption(overlayPackage, shapeDrawable, getShapedIcons(path)));
+ } catch (NameNotFoundException | NotFoundException e) {
+ Log.w(TAG, String.format("Couldn't load shape overlay %s, will skip it",
+ overlayPackage), e);
+ }
+ }
+ }
+
+ private void addDefault() {
+ Resources system = Resources.getSystem();
+ Path path = loadPath(system, ANDROID_PACKAGE);
+ ShapeDrawable shapeDrawable = createShapeDrawable(path);
+ mOptions.add(new ShapeOption(null, shapeDrawable, getShapedIcons(path)));
+ }
+
+ private ShapeDrawable createShapeDrawable(Path path) {
+ PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE);
+ ShapeDrawable shapeDrawable = new ShapeDrawable(shape);
+ shapeDrawable.setIntrinsicHeight(mThumbSize);
+ shapeDrawable.setIntrinsicWidth(mThumbSize);
+ return shapeDrawable;
+ }
+
+ private List<Drawable> getShapedIcons(Path path) {
+ List<Drawable> icons = new ArrayList<>();
+ for (String packageName : mShapePreviewIconPackages) {
+ try {
+ Drawable appIcon = mContext.getPackageManager().getApplicationIcon(packageName);
+ if (appIcon instanceof AdaptiveIconDrawable) {
+ AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) appIcon;
+ icons.add(new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(),
+ adaptiveIcon.getForeground(), path));
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Couldn't find app " + packageName
+ + ", won't use it for icon shape preview");
+ }
+ }
+ return icons;
+ }
+
+ private Path loadPath(Resources overlayRes, String packageName) {
+ String shape = overlayRes.getString(overlayRes.getIdentifier(CONFIG_ICON_MASK, "string",
+ packageName));
+
+ if (!TextUtils.isEmpty(shape)) {
+ return PathParser.createPathFromPathData(shape);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index e1e29cb..058d5bc 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -23,8 +23,12 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -335,28 +339,69 @@
public static class ShapeOption extends ThemeComponentOption {
- ShapeOption(String packageName) {
+ private final LayerDrawable mShape;
+ private final List<Drawable> mAppIcons;
+ private int[] mShapeIconIds = {
+ R.id.shape_preview_icon_0, R.id.shape_preview_icon_1, R.id.shape_preview_icon_2,
+ R.id.shape_preview_icon_3, R.id.shape_preview_icon_4, R.id.shape_preview_icon_5
+ };
+
+ ShapeOption(String packageName, Drawable shapeDrawable, List<Drawable> appIcons) {
addOverlayPackage(OVERLAY_CATEGORY_SHAPE, packageName);
+ mAppIcons = appIcons;
+ Drawable background = shapeDrawable.getConstantState().newDrawable();
+ Drawable foreground = shapeDrawable.getConstantState().newDrawable();
+ mShape = new LayerDrawable(new Drawable[]{background, foreground});
+ mShape.setLayerGravity(0, Gravity.CENTER);
+ mShape.setLayerGravity(1, Gravity.CENTER);
}
@Override
public void bindThumbnailTile(View view) {
+ ImageView thumb = view.findViewById(R.id.shape_thumbnail);
+ Resources res = view.getResources();
+ Theme theme = view.getContext().getTheme();
+ int borderWidth = res.getDimensionPixelSize(R.dimen.component_shape_border_width);
+ Drawable background = mShape.getDrawable(0);
+ background.setTintList(res.getColorStateList(R.color.option_border_color, theme));
+
+ ShapeDrawable foreground = (ShapeDrawable) mShape.getDrawable(1);
+
+ foreground.setIntrinsicHeight(background.getIntrinsicHeight() - borderWidth);
+ foreground.setIntrinsicWidth(background.getIntrinsicWidth() - borderWidth);
+ foreground.setTint(res.getColor(R.color.shape_option_tile_foreground_color, theme));
+
+ thumb.setImageDrawable(mShape);
}
@Override
public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
- return false;
+ CustomThemeManager customThemeManager = (CustomThemeManager) manager;
+ return Objects.equals(getOverlayPackages().get(OVERLAY_CATEGORY_SHAPE),
+ customThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_SHAPE));
}
@Override
public int getLayoutResId() {
- return 0;
+ return R.layout.theme_shape_option;
}
@Override
public void bindPreview(ViewGroup container) {
+ TextView header = container.findViewById(R.id.theme_preview_card_header);
+ header.setText(R.string.preview_name_shape);
+ header.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_shapes_24px, 0, 0);
+ ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
+ if (cardBody.getChildCount() == 0) {
+ LayoutInflater.from(container.getContext()).inflate(
+ R.layout.preview_card_shape_content, cardBody, true);
+ }
+ for (int i = 0; i < mShapeIconIds.length && i < mAppIcons.size(); i++) {
+ ImageView iconView = cardBody.findViewById(mShapeIconIds[i]);
+ iconView.setBackground(mAppIcons.get(i));
+ }
}
}
}
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 50a3534..9797e8e 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -39,10 +39,12 @@
import com.android.customization.model.theme.custom.CustomThemeManager;
import com.android.customization.model.theme.custom.FontOptionsProvider;
import com.android.customization.model.theme.custom.IconOptionsProvider;
+import com.android.customization.model.theme.custom.ShapeOptionsProvider;
import com.android.customization.model.theme.custom.ThemeComponentOption;
import com.android.customization.model.theme.custom.ThemeComponentOption.ColorOption;
import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption;
import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption;
+import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption;
import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
import com.android.customization.module.CustomizationInjector;
import com.android.customization.picker.theme.CustomThemeComponentFragment.CustomThemeComponentFragmentHost;
@@ -108,7 +110,6 @@
// Navigate to the first step
navigateToStep(0);
}
-
}
private void navigateToStep(int i) {
@@ -130,10 +131,11 @@
private void initSteps() {
mSteps = new ArrayList<>();
OverlayManagerCompat manager = new OverlayManagerCompat(this);
- mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0, 3));
- mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1, 3));
+ mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0, 4));
+ mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1, 4));
mSteps.add(new ColorStep(new ColorOptionsProvider(this, manager, mCustomThemeManager),
- 2, 3));
+ 2, 4));
+ mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3, 4));
mCurrentStep = 0;
}
@@ -299,4 +301,21 @@
titleResId);
}
}
+
+ private class ShapeStep extends ComponentStep<ShapeOption> {
+
+ protected ShapeStep(ThemeComponentOptionProvider<ShapeOption> provider,
+ int position, int totalSteps) {
+ super(R.string.shape_component_title, provider, position, totalSteps);
+ }
+
+ @Override
+ CustomThemeComponentFragment createFragment() {
+ return CustomThemeComponentFragment.newInstance(
+ CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
+ position,
+ totalSteps,
+ titleResId);
+ }
+ }
}
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index d5f8abe..b074b0e 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -432,7 +432,7 @@
}
});
}
- if (previewInfo.shapeDrawable != null && !previewInfo.shapeAppIcons.isEmpty()) {
+ if (!previewInfo.shapeAppIcons.isEmpty()) {
addPage(new ThemePreviewPage(activity, R.string.preview_name_shape,
R.drawable.ic_shapes_24px, R.layout.preview_card_shape_content,
previewInfo.resolveAccentColor(res), editClickListener) {