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) {