Merge "Setting targetSdkVersion to 29 for ThemePicker" into ub-launcher3-qt-dev
diff --git a/res/color/edittext_background_color.xml b/res/color/edittext_background_color.xml
new file mode 100644
index 0000000..9191154
--- /dev/null
+++ b/res/color/edittext_background_color.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:alpha="0.16" android:color="@color/edit_background_base" android:state_hovered="true"/>
+    <item android:alpha="0.04" android:color="@color/edit_background_base" android:state_enabled="false"/>
+    <item android:alpha="0.05" android:color="@color/edit_background_base"/>
+</selector>
\ No newline at end of file
diff --git a/res/color/edittext_text_color.xml b/res/color/edittext_text_color.xml
new file mode 100644
index 0000000..174f6ca
--- /dev/null
+++ b/res/color/edittext_text_color.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:textColorPrimaryNoDisable" android:state_focused="true"/>
+    <item android:alpha="0.4" android:color="?android:textColorPrimaryNoDisable"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/edittext_background.xml b/res/drawable/edittext_background.xml
new file mode 100644
index 0000000..8fa91c3
--- /dev/null
+++ b/res/drawable/edittext_background.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/edittext_background_color"/>
+            <corners android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp" android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
+        </shape>
+    </item>
+    <item android:gravity="bottom">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:colorAccent"/>
+            <size android:height="1dp"/>
+        </shape>
+    </item>
+</layer-list>
+
diff --git a/res/layout-land/fragment_custom_theme_name.xml b/res/layout-land/fragment_custom_theme_name.xml
new file mode 100644
index 0000000..0f190a8
--- /dev/null
+++ b/res/layout-land/fragment_custom_theme_name.xml
@@ -0,0 +1,80 @@
+<?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="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="?android:colorPrimary">
+    <include layout="@layout/section_header"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <FrameLayout
+            android:id="@+id/component_preview_container"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="@color/secondary_color">
+            <include
+                android:id="@+id/component_preview_content"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginHorizontal="@dimen/preview_page_horizontal_margin"
+                android:layout_marginTop="@dimen/preview_page_top_margin"
+                android:layout_marginBottom="@dimen/component_preview_page_bottom_margin"
+                layout="@layout/theme_preview_card"/>
+        </FrameLayout>
+        <LinearLayout
+            android:id="@+id/options_section"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:paddingTop="10dp"
+            android:paddingBottom="@dimen/custom_theme_nav_height"
+            android:paddingVertical="10dp"
+            android:clipToPadding="false"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/component_options_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:layout_margin="10dp"
+                android:textAlignment="center"
+                android:textAppearance="@style/HeaderTextAppearance"/>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/options_container_height"
+                android:layout_gravity="center">
+
+                <EditText
+                    style="@style/CustomThemeNameEditText"
+                    android:id="@+id/custom_theme_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:minWidth="300dp"/>
+            </FrameLayout>
+
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/fragment_custom_theme_name.xml b/res/layout/fragment_custom_theme_name.xml
new file mode 100644
index 0000000..98feb60
--- /dev/null
+++ b/res/layout/fragment_custom_theme_name.xml
@@ -0,0 +1,74 @@
+<?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="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:background="?android:colorPrimary">
+    <include layout="@layout/section_header"/>
+
+    <FrameLayout
+        android:id="@+id/component_preview_container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:background="@color/secondary_color">
+        <include
+            android:id="@+id/component_preview_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/preview_page_horizontal_margin"
+            android:layout_marginTop="@dimen/preview_page_top_margin"
+            android:layout_marginBottom="@dimen/component_preview_page_bottom_margin"
+            layout="@layout/theme_preview_card"/>
+    </FrameLayout>
+    <LinearLayout
+        android:id="@+id/options_section"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingVertical="10dp"
+        android:clipToPadding="false"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/component_options_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_margin="10dp"
+            android:textAlignment="center"
+            android:textAppearance="@style/HeaderTextAppearance"/>
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/options_container_height"
+            android:layout_gravity="center">
+
+            <EditText
+                style="@style/CustomThemeNameEditText"
+                android:id="@+id/custom_theme_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:minWidth="300dp"/>
+        </FrameLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 1ddfc10..6a79445 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -38,4 +38,6 @@
     <color name="toolbar_color">#000000</color>
 
     <color name="divider_color">@color/white_14_alpha</color>
+
+    <color name="edit_background_base">@color/material_white_100</color>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ad4c1d2..68ad908 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -57,4 +57,6 @@
     <color name="toolbar_color">@color/material_white_100</color>
 
     <color name="divider_color">@color/black_14_alpha</color>
+
+    <color name="edit_background_base">@color/google_grey700</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 217b0ef..46de146 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -133,18 +133,21 @@
         [CHAR LIMIT=30] -->
     <string name="custom_theme_delete">Delete</string>
 
-    <!-- Title of a page allowing the user to choose a font for a custom theme -->
+    <!-- Title of a page allowing the user to choose a font for a custom theme [CHAR LIMIT=40]-->
     <string name="font_component_title">Choose font</string>
 
-    <!-- Title of a page allowing the user to choose an icon set for a custom theme -->
+    <!-- Title of a page allowing the user to choose an icon set for a custom theme [CHAR LIMIT=40]-->
     <string name="icon_component_title">Choose icons</string>
 
-    <!-- Title of a page allowing the user to choose a color for a custom theme -->
+    <!-- Title of a page allowing the user to choose a color for a custom theme [CHAR LIMIT=40]-->
     <string name="color_component_title">Choose color</string>
 
-    <!-- Title of a page allowing the user to choose an icon shape for a custom theme -->
+    <!-- Title of a page allowing the user to choose an icon shape for a custom theme [CHAR LIMIT=40]-->
     <string name="shape_component_title">Choose shape</string>
 
+    <!-- Title of a page allowing the user to set a name for a custom theme [CHAR LIMIT=40]-->
+    <string name="name_component_title">Name your style</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/res/values/styles.xml b/res/values/styles.xml
index 345a3ee..7ccdbe2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -105,4 +105,15 @@
     <style name="EditLabelStyle">
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault</item>
     </style>
+
+    <style name="CustomThemeNameEditText" parent="@android:style/Widget.DeviceDefault.EditText">
+        <item name="android:background">@drawable/edittext_background</item>
+        <item name="android:paddingTop">16dp</item>
+        <item name="android:paddingBottom">16dp</item>
+        <item name="android:paddingStart">12dp</item>
+        <item name="android:paddingEnd">12dp</item>
+        <item name="android:textColor">@color/edittext_text_color</item>
+        <item name="android:inputType">text</item>
+        <item name="android:maxLength">100</item>
+    </style>
 </resources>
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index c1c6bbe..5123910 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -373,14 +373,15 @@
                 }
             } catch (JSONException e) {
                 Log.w(TAG, "Couldn't read stored custom theme, resetting", e);
-                mThemes.add(new CustomTheme(CustomTheme.newId(), mContext.getString(R.string.custom_theme_title,
-                        customThemesCount + 1), new HashMap<>(), null));
+                mThemes.add(new CustomTheme(CustomTheme.newId(), mContext.getString(
+                        R.string.custom_theme_title, customThemesCount + 1),
+                        new HashMap<>(), null));
             }
         }
 
         // Add an empty one at the end.
-        mThemes.add(new CustomTheme(CustomTheme.newId(), mContext.getString(R.string.custom_theme_title,
-                customThemesCount + 1), new HashMap<>(), null));
+        mThemes.add(new CustomTheme(CustomTheme.newId(), mContext.getString(
+                R.string.custom_theme_title, customThemesCount + 1), new HashMap<>(), null));
 
     }
 
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index 9d2f397..12ec32c 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -49,7 +49,6 @@
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.BitmapCachingAsset;
-import com.android.wallpaper.asset.ResourceAsset;
 import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 
@@ -295,7 +294,8 @@
         @ColorInt private int mColorAccentLight = -1;
         @ColorInt private int mColorAccentDark = -1;
         private List<Drawable> mIcons = new ArrayList<>();
-        private String mShapePath;
+        private String mPathString;
+        private Path mShapePath;
         private boolean mIsDefault;
         @Dimension private int mCornerRadius;
         private Asset mWallpaperAsset;
@@ -308,11 +308,14 @@
                     createPreviewInfo(context));
         }
 
-        protected PreviewInfo createPreviewInfo(Context context) {
+        public PreviewInfo createPreviewInfo(Context context) {
             ShapeDrawable shapeDrawable = null;
             List<Drawable> shapeIcons = new ArrayList<>();
-            if (!TextUtils.isEmpty(mShapePath)) {
-                Path path = PathParser.createPathFromPathData(mShapePath);
+            Path path = mShapePath;
+            if (!TextUtils.isEmpty(mPathString)) {
+                path = PathParser.createPathFromPathData(mPathString);
+            }
+            if (path != null) {
                 PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE);
                 shapeDrawable = new ShapeDrawable(shape);
                 shapeDrawable.setIntrinsicHeight((int) PATH_SIZE);
@@ -322,6 +325,8 @@
                         AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;
                         shapeIcons.add(new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(),
                                 adaptiveIcon.getForeground(), path));
+                    } else if (icon instanceof DynamicAdaptiveIconDrawable) {
+                        shapeIcons.add(icon);
                     }
                     // TODO: add iconloader library's legacy treatment helper methods for
                     //  non-adaptive icons
@@ -332,6 +337,10 @@
                     mWallpaperAsset, shapeIcons);
         }
 
+        public Map<String, String> getPackages() {
+            return Collections.unmodifiableMap(mPackages);
+        }
+
         public String getTitle() {
             return mTitle;
         }
@@ -372,6 +381,11 @@
         }
 
         public Builder setShapePath(String path) {
+            mPathString = path;
+            return this;
+        }
+
+        public Builder setShapePath(Path path) {
             mShapePath = path;
             return this;
         }
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index de6a98b..d873972 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -164,7 +164,7 @@
     private void applyOverlays(ThemeBundle theme, Callback callback) {
         boolean allApplied = Settings.Secure.putString(mActivity.getContentResolver(),
                 ResourceConstants.THEME_SETTING, theme.getSerializedPackages());
-        if (theme instanceof CustomTheme && !((CustomTheme) theme).isDefined()) {
+        if (theme instanceof CustomTheme) {
             storeCustomTheme((CustomTheme) theme);
         }
         mCurrentOverlays = null;
diff --git a/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java b/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java
index 07a876b..020e3ef 100644
--- a/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/ColorOptionsProvider.java
@@ -77,7 +77,7 @@
         String iconPackage =
                 mCustomThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_ICON_ANDROID);
         if (TextUtils.isEmpty(iconPackage)) {
-            iconPackage = SYSUI_PACKAGE;
+            iconPackage = ANDROID_PACKAGE;
         }
         for (String iconName : ICONS_FOR_PREVIEW) {
             try {
diff --git a/src/com/android/customization/model/theme/custom/CustomTheme.java b/src/com/android/customization/model/theme/custom/CustomTheme.java
index 15e4eb8..97a1876 100644
--- a/src/com/android/customization/model/theme/custom/CustomTheme.java
+++ b/src/com/android/customization/model/theme/custom/CustomTheme.java
@@ -34,6 +34,9 @@
         return UUID.randomUUID().toString();
     }
 
+    /**
+     * Used to uniquely identify a custom theme since names can change.
+     */
     private final String mId;
 
     public CustomTheme(@NonNull String id, String title, Map<String, String> overlayPackages,
diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
index 0cc0161..a3acba4 100644
--- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java
+++ b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
@@ -21,7 +21,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
 import com.android.customization.model.theme.ThemeManager;
+import com.android.customization.model.theme.custom.CustomTheme.Builder;
 import com.android.wallpaper.R;
 
 import java.util.HashMap;
@@ -29,12 +31,13 @@
 
 public class CustomThemeManager implements CustomizationManager<ThemeComponentOption> {
 
-    private final Map<String, String> mOverlayPackages = new HashMap<>();
     private final CustomTheme mOriginalTheme;
+    private final CustomTheme.Builder mBuilder;
 
     private CustomThemeManager(Map<String, String> overlayPackages,
             @Nullable CustomTheme originalTheme) {
-        mOverlayPackages.putAll(overlayPackages);
+        mBuilder = new Builder();
+        overlayPackages.forEach(mBuilder::addOverlayPackage);
         mOriginalTheme = originalTheme;
     }
 
@@ -45,22 +48,23 @@
 
     @Override
     public void apply(ThemeComponentOption option, @Nullable Callback callback) {
-        option.getOverlayPackages().forEach((category, packageName) -> {
-            if (!TextUtils.isEmpty(packageName)) {
-                mOverlayPackages.put(category, packageName);
-            }
-        });
+//        option.getOverlayPackages().forEach((category, packageName) -> {
+//            if (!TextUtils.isEmpty(packageName)) {
+//                mBuilder.addOverlayPackage(category, packageName);
+//            }
+//        });
+        option.buildStep(mBuilder);
         if (callback != null) {
             callback.onSuccess();
         }
     }
 
     public Map<String, String> getOverlayPackages() {
-        return mOverlayPackages;
+        return mBuilder.getPackages();
     }
 
-    public CustomTheme buildPartialCustomTheme(String id, String title) {
-        return new CustomTheme(id, title, mOverlayPackages, null);
+    public CustomTheme buildPartialCustomTheme(Context context, String id, String title) {
+        return ((CustomTheme.Builder)mBuilder.setId(id).setTitle(title)).build(context);
     }
 
     @Override
@@ -72,6 +76,10 @@
         return mOriginalTheme;
     }
 
+    public PreviewInfo buildCustomThemePreviewInfo(Context context) {
+        return mBuilder.createPreviewInfo(context);
+    }
+
     public static CustomThemeManager create(
             @Nullable CustomTheme customTheme, ThemeManager themeManager) {
         if (customTheme != null && customTheme.isDefined()) {
@@ -80,4 +88,5 @@
         // Seed the first custom theme with the currently applied theme.
         return new CustomThemeManager(themeManager.getCurrentOverlays(), customTheme);
     }
+
 }
diff --git a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
index 112ed43..b050e41 100644
--- a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
@@ -16,9 +16,8 @@
 package com.android.customization.model.theme.custom;
 
 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.CONFIG_CORNERRADIUS;
 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;
 
@@ -28,7 +27,6 @@
 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;
@@ -36,6 +34,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Dimension;
 import androidx.core.graphics.PathParser;
 
 import com.android.customization.model.ResourceConstants;
@@ -75,8 +74,8 @@
                 ShapeDrawable shapeDrawable = createShapeDrawable(path);
                 PackageManager pm = mContext.getPackageManager();
                 String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString();
-                mOptions.add(new ShapeOption(overlayPackage, label, shapeDrawable,
-                        getShapedIcons(path)));
+                mOptions.add(new ShapeOption(overlayPackage, label, path,
+                        loadCornerRadius(overlayPackage), shapeDrawable, getShapedIcons(path)));
             } catch (NameNotFoundException | NotFoundException e) {
                 Log.w(TAG, String.format("Couldn't load shape overlay %s, will skip it",
                         overlayPackage), e);
@@ -88,7 +87,10 @@
         Resources system = Resources.getSystem();
         Path path = loadPath(system, ANDROID_PACKAGE);
         ShapeDrawable shapeDrawable = createShapeDrawable(path);
-        mOptions.add(new ShapeOption(null, mContext.getString(R.string.default_theme_title),
+        mOptions.add(new ShapeOption(null, mContext.getString(R.string.default_theme_title), path,
+                system.getDimensionPixelOffset(
+                    system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
+                        "dimen", ResourceConstants.ANDROID_PACKAGE)),
                 shapeDrawable, getShapedIcons(path)));
     }
 
@@ -127,4 +129,15 @@
         }
         return null;
     }
+
+    @Dimension
+    private int loadCornerRadius(String packageName)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier(
+                CONFIG_CORNERRADIUS, "dimen", packageName));
+    }
 }
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index 2dc163e..994b5f3 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -17,6 +17,11 @@
 
 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_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_ICON_THEMEPICKER;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
 
 import android.content.Context;
@@ -24,10 +29,12 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
+import android.graphics.Path;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.ShapeDrawable;
+import android.text.TextUtils;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -39,17 +46,20 @@
 import android.widget.TextView;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.Dimension;
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
 import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.custom.CustomTheme.Builder;
 import com.android.wallpaper.R;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 
 /**
@@ -79,6 +89,15 @@
 
     public abstract void bindPreview(ViewGroup container);
 
+    public Builder buildStep(Builder builder) {
+        getOverlayPackages().forEach((category, packageName) -> {
+            if (!TextUtils.isEmpty(packageName)) {
+                builder.addOverlayPackage(category, packageName);
+            }
+        });
+        return builder;
+    }
+
     public static class FontOption extends ThemeComponentOption {
 
         private final String mLabel;
@@ -134,6 +153,12 @@
             TextView bodyText = container.findViewById(R.id.font_card_body);
             bodyText.setTypeface(mBodyFont);
         }
+
+        @Override
+        public Builder buildStep(Builder builder) {
+            builder.setHeadlineFontFamily(mHeadlineFont).setBodyFontFamily(mBodyFont);
+            return super.buildStep(builder);
+        }
     }
 
     public static class IconOption extends ThemeComponentOption {
@@ -161,16 +186,21 @@
         @Override
         public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
             CustomThemeManager customThemeManager = (CustomThemeManager) manager;
+            Map<String, String> themePackages = customThemeManager.getOverlayPackages();
             if (getOverlayPackages().isEmpty()) {
-                return customThemeManager.getOverlayPackages().isEmpty();
+                return themePackages.get(OVERLAY_CATEGORY_ICON_SYSUI) == null &&
+                        themePackages.get(OVERLAY_CATEGORY_ICON_SETTINGS) == null &&
+                        themePackages.get(OVERLAY_CATEGORY_ICON_ANDROID) == null &&
+                        themePackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER) == null &&
+                        themePackages.get(OVERLAY_CATEGORY_ICON_THEMEPICKER) == null;
             }
-             for (Map.Entry<String, String> overlayEntry : getOverlayPackages().entrySet()) {
-                 if(!Objects.equals(overlayEntry.getValue(),
-                         customThemeManager.getOverlayPackages().get(overlayEntry.getKey()))) {
-                     return false;
-                 }
-             }
-             return true;
+            for (Map.Entry<String, String> overlayEntry : getOverlayPackages().entrySet()) {
+                if(!Objects.equals(overlayEntry.getValue(),
+                        themePackages.get(overlayEntry.getKey()))) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         @Override
@@ -210,6 +240,14 @@
         public void setLabel(String label) {
             mLabel = label;
         }
+
+        @Override
+        public Builder buildStep(Builder builder) {
+            for (Drawable icon : mIcons) {
+                builder.addIcon(icon);
+            }
+            return super.buildStep(builder);
+        }
     }
 
     public static class ColorOption extends ThemeComponentOption {
@@ -346,6 +384,12 @@
         public void setShapeDrawable(@Nullable Drawable shapeDrawable) {
             mShapeDrawable = shapeDrawable;
         }
+
+        @Override
+        public Builder buildStep(Builder builder) {
+            builder.setColorAccentDark(mColorAccentDark).setColorAccentLight(mColorAccentLight);
+            return super.buildStep(builder);
+        }
     }
 
     public static class ShapeOption extends ThemeComponentOption {
@@ -353,16 +397,21 @@
         private final LayerDrawable mShape;
         private final List<Drawable> mAppIcons;
         private final String mLabel;
+        private final Path mPath;
+        private final int mCornerRadius;
         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, String label, Drawable shapeDrawable,
+        ShapeOption(String packageName, String label, Path path,
+                @Dimension int cornerRadius, Drawable shapeDrawable,
                 List<Drawable> appIcons) {
             addOverlayPackage(OVERLAY_CATEGORY_SHAPE, packageName);
             mLabel = label;
             mAppIcons = appIcons;
+            mPath = path;
+            mCornerRadius = cornerRadius;
             Drawable background = shapeDrawable.getConstantState().newDrawable();
             Drawable foreground = shapeDrawable.getConstantState().newDrawable();
             mShape = new LayerDrawable(new Drawable[]{background, foreground});
@@ -418,5 +467,14 @@
                 iconView.setBackground(mAppIcons.get(i));
             }
         }
+
+        @Override
+        public Builder buildStep(Builder builder) {
+            builder.setShapePath(mPath).setBottomSheetCornerRadius(mCornerRadius);
+            for (Drawable appIcon : mAppIcons) {
+                builder.addShapePreviewIcon(appIcon);
+            }
+            return super.buildStep(builder);
+        }
     }
 }
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 0ea9617..2bc371d 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -16,6 +16,7 @@
 package com.android.customization.picker.theme;
 
 import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
@@ -34,7 +35,6 @@
 import com.android.customization.model.theme.DefaultThemeProvider;
 import com.android.customization.model.theme.OverlayManagerCompat;
 import com.android.customization.model.theme.ThemeBundle;
-import com.android.customization.model.theme.ThemeBundle.Builder;
 import com.android.customization.model.theme.ThemeBundleProvider;
 import com.android.customization.model.theme.ThemeManager;
 import com.android.customization.model.theme.custom.ColorOptionsProvider;
@@ -51,7 +51,7 @@
 import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
 import com.android.customization.module.CustomizationInjector;
 import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.theme.CustomThemeComponentFragment.CustomThemeComponentFragmentHost;
+import com.android.customization.picker.theme.CustomThemeStepFragment.CustomThemeComponentStepHost;
 import com.android.wallpaper.R;
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.WallpaperSetter;
@@ -62,7 +62,7 @@
 import java.util.List;
 
 public class CustomThemeActivity extends FragmentActivity implements
-        CustomThemeComponentFragmentHost {
+        CustomThemeComponentStepHost {
     public static final String EXTRA_THEME_ID = "CustomThemeActivity.ThemeId";
     public static final String EXTRA_THEME_TITLE = "CustomThemeActivity.ThemeTitle";
     public static final String EXTRA_THEME_PACKAGES = "CustomThemeActivity.ThemePackages";
@@ -160,59 +160,64 @@
     private void initSteps(int currentStep) {
         mSteps = new ArrayList<>();
         OverlayManagerCompat manager = new OverlayManagerCompat(this);
-        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, 4));
-        mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3, 4));        
+        mSteps.add(new FontStep(new FontOptionsProvider(this, manager), 0));
+        mSteps.add(new IconStep(new IconOptionsProvider(this, manager), 1));
+        mSteps.add(new ColorStep(new ColorOptionsProvider(this, manager, mCustomThemeManager), 2));
+        mSteps.add(new ShapeStep(new ShapeOptionsProvider(this, manager), 3));
+        mSteps.add(new NameStep(4));
         mCurrentStep = currentStep;
     }
 
     private void onNextOrApply() {
-        mCustomThemeManager.apply(getCurrentStepFragment().getSelectedOption(), new Callback() {
-            @Override
-            public void onSuccess() {
-                if (mCurrentStep < mSteps.size() - 1) {
+        CustomThemeStepFragment stepFragment = getCurrentStepFragment();
+        if (stepFragment instanceof CustomThemeComponentFragment) {
+            CustomThemeComponentFragment fragment = (CustomThemeComponentFragment) stepFragment;
+            mCustomThemeManager.apply(fragment.getSelectedOption(), new Callback() {
+                @Override
+                public void onSuccess() {
                     navigateToStep(mCurrentStep + 1);
-                } else {
-                    CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme();
-
-                    // We're on the last step, apply theme and leave
-                    CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(
-                            originalTheme.getId(), originalTheme.getTitle());
-
-                    // If the current theme is equal to the original theme being edited, then
-                    // don't search for an equivalent, let the user apply the same one by keeping
-                    // it null.
-                    ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply))
-                                ? null : mThemeManager.findThemeByPackages(themeToApply);
-
-                    if (equivalent != null) {
-                        AlertDialog.Builder builder =
-                                new AlertDialog.Builder(CustomThemeActivity.this);
-                        builder.setTitle(getString(R.string.use_style_instead_title,
-                                    equivalent.getTitle()))
-                                .setMessage(getString(R.string.use_style_instead_body,
-                                        equivalent.getTitle()))
-                                .setPositiveButton(getString(R.string.use_style_button,
-                                        equivalent.getTitle()),
-                                        (dialogInterface, i) -> applyTheme(equivalent))
-                                .setNegativeButton(R.string.no_thanks, null)
-                                .create()
-                                .show();
-                    } else {
-                        applyTheme(themeToApply);
-                    }
                 }
-            }
 
-            @Override
-            public void onError(@Nullable Throwable throwable) {
-                Log.w(TAG, "Error applying custom theme component", throwable);
-                Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg,
-                        Toast.LENGTH_LONG).show();
+                @Override
+                public void onError(@Nullable Throwable throwable) {
+                    Log.w(TAG, "Error applying custom theme component", throwable);
+                    Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg,
+                            Toast.LENGTH_LONG).show();
+                }
+            });
+        } else if (stepFragment instanceof CustomThemeNameFragment) {
+            CustomThemeNameFragment fragment = (CustomThemeNameFragment) stepFragment;
+            CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme();
+
+            // We're on the last step, apply theme and leave
+            CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(this,
+                    originalTheme.getId(), fragment.getThemeName());
+
+            // If the current theme is equal to the original theme being edited, then
+            // don't search for an equivalent, let the user apply the same one by keeping
+            // it null.
+            ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply))
+                    ? null : mThemeManager.findThemeByPackages(themeToApply);
+
+            if (equivalent != null) {
+                Builder builder =
+                        new Builder(CustomThemeActivity.this);
+                builder.setTitle(getString(R.string.use_style_instead_title,
+                        equivalent.getTitle()))
+                        .setMessage(getString(R.string.use_style_instead_body,
+                                equivalent.getTitle()))
+                        .setPositiveButton(getString(R.string.use_style_button,
+                                equivalent.getTitle()),
+                                (dialogInterface, i) -> applyTheme(equivalent))
+                        .setNegativeButton(R.string.no_thanks, null)
+                        .create()
+                        .show();
+            } else {
+                applyTheme(themeToApply);
             }
-        });
+        } else {
+            throw new IllegalStateException("Unknown CustomThemeStepFragment");
+        }
     }
 
     private void applyTheme(ThemeBundle themeToApply) {
@@ -235,8 +240,8 @@
         });
     }
 
-    private CustomThemeComponentFragment getCurrentStepFragment() {
-        return (CustomThemeComponentFragment)
+    private CustomThemeStepFragment getCurrentStepFragment() {
+        return (CustomThemeStepFragment)
                 getSupportFragmentManager().findFragmentById(R.id.fragment_container);
     }
 
@@ -284,7 +289,7 @@
         @StringRes final int titleResId;
         final ThemeComponentOptionProvider<T> provider;
         final int position;
-        private CustomThemeComponentFragment mFragment;
+        private CustomThemeStepFragment mFragment;
 
         protected ComponentStep(@StringRes int titleResId, ThemeComponentOptionProvider<T> provider,
                                 int position) {
@@ -293,7 +298,7 @@
             this.position = position;
         }
 
-        CustomThemeComponentFragment getFragment(String title) {
+        CustomThemeStepFragment getFragment(String title) {
             if (mFragment == null) {
                 mFragment = createFragment(title);
             }
@@ -303,13 +308,13 @@
         /**
          * @return a newly created fragment that will handle this step's UI.
          */
-        abstract CustomThemeComponentFragment createFragment(String title);
+        abstract CustomThemeStepFragment createFragment(String title);
     }
 
     private class FontStep extends ComponentStep<FontOption> {
 
         protected FontStep(ThemeComponentOptionProvider<FontOption> provider,
-                int position, int totalSteps) {
+                int position) {
             super(R.string.font_component_title, provider, position);
         }
 
@@ -325,7 +330,7 @@
     private class IconStep extends ComponentStep<IconOption> {
 
         protected IconStep(ThemeComponentOptionProvider<IconOption> provider,
-                int position, int totalSteps) {
+                int position) {
             super(R.string.icon_component_title, provider, position);
         }
 
@@ -341,7 +346,7 @@
     private class ColorStep extends ComponentStep<ColorOption> {
 
         protected ColorStep(ThemeComponentOptionProvider<ColorOption> provider,
-                int position, int totalSteps) {
+                int position) {
             super(R.string.color_component_title, provider, position);
         }
 
@@ -358,7 +363,7 @@
     private class ShapeStep extends ComponentStep<ShapeOption> {
 
         protected ShapeStep(ThemeComponentOptionProvider<ShapeOption> provider,
-                int position, int totalSteps) {
+                int position) {
             super(R.string.shape_component_title, provider, position);
         }
 
@@ -370,4 +375,19 @@
                     titleResId);
         }
     }
+
+    private class NameStep extends ComponentStep {
+
+        protected NameStep(int position) {
+            super(R.string.name_component_title, null, position);
+        }
+
+        @Override
+        CustomThemeNameFragment createFragment(String title) {
+            return CustomThemeNameFragment.newInstance(
+                    title,
+                    position,
+                    titleResId);
+        }
+    }
 }
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
index a50051b..f6471f0 100644
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
@@ -37,22 +37,8 @@
 import com.android.wallpaper.R;
 import com.android.wallpaper.picker.ToolbarFragment;
 
-public class CustomThemeComponentFragment extends ToolbarFragment {
-    private static final String ARG_KEY_POSITION = "CustomThemeComponentFragment.position";
-    private static final String ARG_KEY_TITLE_RES_ID = "CustomThemeComponentFragment.title_res";
+public class CustomThemeComponentFragment extends CustomThemeStepFragment {
     private static final String ARG_USE_GRID_LAYOUT = "CustomThemeComponentFragment.use_grid";;
-    private CustomThemeComponentFragmentHost mHost;
-
-    public interface CustomThemeComponentFragmentHost {
-        void delete();
-        void cancel();
-        ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider(
-                int position);
-
-        CustomThemeManager getCustomThemeManager();
-
-        void setCurrentStep(int step);
-    }
 
     public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position,
             int titleResId) {
@@ -71,53 +57,24 @@
     }
 
     private ThemeComponentOptionProvider<? extends ThemeComponentOption> mProvider;
-    private CustomThemeManager mCustomThemeManager;
-    private int mPosition;
-    @StringRes private int mTitleResId;
     private boolean mUseGridLayout;
 
     private RecyclerView mOptionsContainer;
     private OptionSelectorController<ThemeComponentOption> mOptionsController;
-    private ViewGroup mPreviewContainer;
-    private TextView mTitle;
     private ThemeComponentOption mSelectedOption;
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mPosition = getArguments().getInt(ARG_KEY_POSITION);
-        mTitleResId = getArguments().getInt(ARG_KEY_TITLE_RES_ID);
         mUseGridLayout = getArguments().getBoolean(ARG_USE_GRID_LAYOUT);
         mProvider = mHost.getComponentOptionProvider(mPosition);
-        mCustomThemeManager = mHost.getCustomThemeManager();
-    }
-
-    @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        mHost = (CustomThemeComponentFragmentHost) context;
     }
 
     @Nullable
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
-        View view = inflater.inflate(
-                R.layout.fragment_custom_theme_component, container, /* attachToRoot */ false);
-        // No original theme means it's a new one, so no toolbar icon for deleting it is needed
-        if (mCustomThemeManager.getOriginalTheme() == null) {
-            setUpToolbar(view);
-        } else {
-            setUpToolbar(view, R.menu.custom_theme_editor_menu);
-            mToolbar.getMenu().getItem(0).setIconTintList(
-                    getContext().getColorStateList(R.color.toolbar_icon_color));
-        }
-        Drawable closeIcon = getResources().getDrawable(R.drawable.ic_close_24px, null).mutate();
-        closeIcon.setTintList(getResources().getColorStateList(R.color.toolbar_icon_color, null));
-        mToolbar.setNavigationIcon(closeIcon);
-
-        mToolbar.setNavigationContentDescription(R.string.cancel);
-        mToolbar.setNavigationOnClickListener(v -> mHost.cancel());
+        View view = super.onCreateView(inflater, container, savedInstanceState);
         mOptionsContainer = view.findViewById(R.id.options_container);
         mPreviewContainer = view.findViewById(R.id.component_preview_content);
         mTitle = view.findViewById(R.id.component_options_title);
@@ -128,24 +85,8 @@
     }
 
     @Override
-    public void onResume() {
-        super.onResume();
-        mHost.setCurrentStep(mPosition);
-    }
-
-    @Override
-    public boolean onMenuItemClick(MenuItem item) {
-        if (item.getItemId() == R.id.custom_theme_delete) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-            builder.setMessage(R.string.delete_custom_theme_confirmation)
-                    .setPositiveButton(R.string.delete_custom_theme_button,
-                            (dialogInterface, i) -> mHost.delete())
-                    .setNegativeButton(R.string.cancel, null)
-                    .create()
-                    .show();
-            return true;
-        }
-        return super.onMenuItemClick(item);
+    protected int getFragmentLayoutResId() {
+        return R.layout.fragment_custom_theme_component;
     }
 
     public ThemeComponentOption getSelectedOption() {
diff --git a/src/com/android/customization/picker/theme/CustomThemeNameFragment.java b/src/com/android/customization/picker/theme/CustomThemeNameFragment.java
new file mode 100644
index 0000000..84915ff
--- /dev/null
+++ b/src/com/android/customization/picker/theme/CustomThemeNameFragment.java
@@ -0,0 +1,175 @@
+/*
+ * 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.picker.theme;
+
+import android.app.WallpaperColors;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.cardview.widget.CardView;
+
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
+import com.android.customization.model.theme.custom.CustomTheme;
+import com.android.customization.picker.theme.ThemePreviewPage.ThemeCoverPage;
+import com.android.wallpaper.R;
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.BitmapCachingAsset;
+import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.picker.ToolbarFragment;
+
+public class CustomThemeNameFragment extends CustomThemeStepFragment {
+
+    public static CustomThemeNameFragment newInstance(CharSequence toolbarTitle, int position,
+            int titleResId) {
+        CustomThemeNameFragment fragment = new CustomThemeNameFragment();
+        Bundle arguments = ToolbarFragment.createArguments(toolbarTitle);
+        arguments.putInt(ARG_KEY_POSITION, position);
+        arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId);
+        fragment.setArguments(arguments);
+        return fragment;
+    }
+
+
+    private int[] mColorButtonIds = {
+            R.id.preview_check_selected, R.id.preview_radio_selected, R.id.preview_toggle_selected
+    };
+    private int[] mColorTileIds = {
+            R.id.preview_color_qs_0_bg, R.id.preview_color_qs_1_bg, R.id.preview_color_qs_2_bg
+    };
+    private int[] mColorTileIconIds = {
+            R.id.preview_color_qs_0_icon, R.id.preview_color_qs_1_icon, R.id.preview_color_qs_2_icon
+    };
+
+    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
+    };
+
+    private Asset mWallpaperAsset;
+    private ThemeCoverPage mCoverPage;
+
+    private EditText mNameEditor;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        CurrentWallpaperInfoFactory currentWallpaperFactory = InjectorProvider.getInjector()
+                .getCurrentWallpaperFactory(getActivity().getApplicationContext());
+        currentWallpaperFactory.createCurrentWallpaperInfos(
+                (homeWallpaper, lockWallpaper, presentationMode) -> {
+                    mWallpaperAsset = new BitmapCachingAsset(getContext(),
+                            homeWallpaper.getThumbAsset(getContext()));
+                    if (mCoverPage != null) {
+                        mCoverPage.bindBody(true);
+                    }
+                }, false);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        mTitle = view.findViewById(R.id.component_options_title);
+        mTitle.setText(mTitleResId);
+        mNameEditor = view.findViewById(R.id.custom_theme_name);
+        mNameEditor.setText(mCustomThemeManager.getOriginalTheme().getTitle());
+        bindCover(view.findViewById(R.id.component_preview_content));
+
+        return view;
+    }
+
+    private void bindCover(CardView card) {
+        PreviewInfo previewInfo = mCustomThemeManager.buildCustomThemePreviewInfo(getContext());
+        mCoverPage = new ThemeCoverPage(getContext(), getThemeName(),
+                previewInfo.resolveAccentColor(getResources()), previewInfo.icons,
+                previewInfo.headlineFontFamily, previewInfo.bottomSheeetCornerRadius,
+                previewInfo.shapeDrawable, previewInfo.shapeAppIcons, null,
+                mColorButtonIds, mColorTileIds, mColorTileIconIds, mShapeIconIds,
+                new WallpaperLayoutListener());
+        mCoverPage.setCard(card);
+        mCoverPage.bindPreviewContent();
+        mNameEditor.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+                ((TextView)card.findViewById(R.id.theme_preview_card_header)).setText(charSequence);
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+
+            }
+        });
+    }
+
+    private class WallpaperLayoutListener implements OnLayoutChangeListener {
+        @Override
+        public void onLayoutChange(View view, int left, int top, int right,
+                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            int targetWidth = right - left;
+            int targetHeight = bottom - top;
+            if (targetWidth > 0 && targetHeight > 0) {
+                if (mWallpaperAsset != null) {
+                    mWallpaperAsset.decodeBitmap(
+                            targetWidth, targetHeight,
+                            bitmap -> setWallpaperBitmap(view, bitmap));
+                }
+                view.removeOnLayoutChangeListener(this);
+            }
+        }
+
+        private void setWallpaperBitmap(View view, Bitmap bitmap) {
+            Resources res = view.getContext().getResources();
+            BitmapDrawable background = new BitmapDrawable(res, bitmap);
+            background.setAlpha(128);
+            view.findViewById(R.id.theme_preview_card_background).setBackground(background);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mCoverPage = null;
+    }
+
+    @Override
+    protected int getFragmentLayoutResId() {
+        return R.layout.fragment_custom_theme_name;
+    }
+
+    public String getThemeName() {
+        return mNameEditor.getText().toString();
+    }
+}
diff --git a/src/com/android/customization/picker/theme/CustomThemeStepFragment.java b/src/com/android/customization/picker/theme/CustomThemeStepFragment.java
new file mode 100644
index 0000000..ebf55e3
--- /dev/null
+++ b/src/com/android/customization/picker/theme/CustomThemeStepFragment.java
@@ -0,0 +1,106 @@
+package com.android.customization.picker.theme;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import com.android.customization.model.theme.custom.CustomThemeManager;
+import com.android.customization.model.theme.custom.ThemeComponentOption;
+import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
+import com.android.wallpaper.R;
+import com.android.wallpaper.picker.ToolbarFragment;
+
+abstract class CustomThemeStepFragment extends ToolbarFragment {
+    protected static final String ARG_KEY_POSITION = "CustomThemeStepFragment.position";
+    protected static final String ARG_KEY_TITLE_RES_ID = "CustomThemeStepFragment.title_res";
+    protected CustomThemeComponentStepHost mHost;
+    protected CustomThemeManager mCustomThemeManager;
+    protected int mPosition;
+    protected ViewGroup mPreviewContainer;
+    protected TextView mTitle;
+    @StringRes
+    protected int mTitleResId;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mHost = (CustomThemeComponentStepHost) context;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mHost.setCurrentStep(mPosition);
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPosition = getArguments().getInt(ARG_KEY_POSITION);
+        mTitleResId = getArguments().getInt(ARG_KEY_TITLE_RES_ID);
+        mCustomThemeManager = mHost.getCustomThemeManager();
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(
+                getFragmentLayoutResId(), container, /* attachToRoot */ false);
+        // No original theme means it's a new one, so no toolbar icon for deleting it is needed
+        if (mCustomThemeManager.getOriginalTheme() == null
+                || !mCustomThemeManager.getOriginalTheme().isDefined()) {
+            setUpToolbar(view);
+        } else {
+            setUpToolbar(view, R.menu.custom_theme_editor_menu);
+            mToolbar.getMenu().getItem(0).setIconTintList(
+                    getContext().getColorStateList(R.color.toolbar_icon_color));
+        }
+        Drawable closeIcon = getResources().getDrawable(R.drawable.ic_close_24px, null).mutate();
+        closeIcon.setTintList(getResources().getColorStateList(R.color.toolbar_icon_color, null));
+        mToolbar.setNavigationIcon(closeIcon);
+
+        mToolbar.setNavigationContentDescription(R.string.cancel);
+        mToolbar.setNavigationOnClickListener(v -> mHost.cancel());
+
+        mPreviewContainer = view.findViewById(R.id.component_preview_content);
+        return view;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        if (item.getItemId() == R.id.custom_theme_delete) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            builder.setMessage(R.string.delete_custom_theme_confirmation)
+                    .setPositiveButton(R.string.delete_custom_theme_button,
+                            (dialogInterface, i) -> mHost.delete())
+                    .setNegativeButton(R.string.cancel, null)
+                    .create()
+                    .show();
+            return true;
+        }
+        return super.onMenuItemClick(item);
+    }
+
+    protected abstract int getFragmentLayoutResId();
+
+    public interface CustomThemeComponentStepHost {
+        void delete();
+        void cancel();
+        ThemeComponentOptionProvider<? extends ThemeComponentOption> getComponentOptionProvider(
+                int position);
+
+        CustomThemeManager getCustomThemeManager();
+
+        void setCurrentStep(int step);
+    }
+}
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index 5b590f0..134d255 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -22,17 +22,12 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.PorterDuff;
-import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.icu.text.DateFormat;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.MeasureSpec;
 import android.view.View.OnClickListener;
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
@@ -44,12 +39,8 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import androidx.annotation.ColorInt;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.customization.model.CustomizationManager.Callback;
@@ -59,7 +50,7 @@
 import com.android.customization.model.theme.custom.CustomTheme;
 import com.android.customization.module.ThemesUserEventLogger;
 import com.android.customization.picker.BasePreviewAdapter;
-import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
+import com.android.customization.picker.theme.ThemePreviewPage.ThemeCoverPage;
 import com.android.customization.widget.OptionSelectorController;
 import com.android.customization.widget.PreviewPager;
 import com.android.wallpaper.R;
@@ -69,10 +60,7 @@
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.picker.ToolbarFragment;
 
-import java.text.FieldPosition;
-import java.util.Calendar;
 import java.util.List;
-import java.util.TimeZone;
 
 /**
  * Fragment that contains the main UI for selecting and applying a ThemeBundle.
@@ -301,143 +289,6 @@
         startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME);
     }
 
-    private static abstract class ThemePreviewPage extends PreviewPage {
-        @StringRes final int nameResId;
-        @DrawableRes final int iconSrc;
-        @LayoutRes final int contentLayoutRes;
-        @ColorInt final int accentColor;
-        protected final LayoutInflater inflater;
-
-        private ThemePreviewPage(Context context, @StringRes int titleResId,
-                @DrawableRes int iconSrc, @LayoutRes int contentLayoutRes,
-                @ColorInt int accentColor) {
-            super(null);
-            this.nameResId = titleResId;
-            this.iconSrc = iconSrc;
-            this.contentLayoutRes = contentLayoutRes;
-            this.accentColor = accentColor;
-            this.inflater = LayoutInflater.from(context);
-        }
-
-        @Override
-        public void bindPreviewContent() {
-            TextView header = card.findViewById(R.id.theme_preview_card_header);
-            header.setText(nameResId);
-            header.setCompoundDrawablesWithIntrinsicBounds(0, iconSrc, 0, 0);
-            header.setCompoundDrawableTintList(ColorStateList.valueOf(accentColor));
-            card.findViewById(R.id.theme_preview_top_bar).setVisibility(View.GONE);
-            card.findViewById(R.id.edit_label).setVisibility(View.GONE);
-
-            ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
-            inflater.inflate(contentLayoutRes, body, true);
-            bindBody(false);
-        }
-
-        protected boolean containsWallpaper() {
-            return false;
-        }
-
-        protected abstract void bindBody(boolean forceRebind);
-    }
-
-    private static class ThemeCoverPage extends ThemePreviewPage {
-
-        private final Typeface mHeadlineFont;
-        private final List<Drawable> mIcons;
-        private String mTitle;
-        private OnClickListener mEditClickListener;
-        private final ThemePreviewAdapter.WallpaperPreviewLayoutListener mListener;
-        private final int mCornerRadius;
-
-        private ThemeCoverPage(Context context, String title, int accentColor, List<Drawable> icons,
-                Typeface headlineFont, int cornerRadius,
-                OnClickListener editClickListener,
-                ThemePreviewAdapter.WallpaperPreviewLayoutListener wallpaperListener) {
-            super(context, 0, 0, R.layout.preview_card_cover_content, accentColor);
-            mTitle = title;
-            mHeadlineFont = headlineFont;
-            mIcons = icons;
-            mCornerRadius = cornerRadius;
-            mEditClickListener = editClickListener;
-            mListener = wallpaperListener;
-        }
-
-        @Override
-        protected void bindBody(boolean forceRebind) {
-            card.addOnLayoutChangeListener(mListener);
-            if (forceRebind) {
-                card.requestLayout();
-            }
-        }
-
-        @Override
-        public void bindPreviewContent() {
-            TextView header = card.findViewById(R.id.theme_preview_card_header);
-            header.setText(mTitle);
-            header.setTextAppearance(R.style.CoverTitleTextAppearance);
-            header.setTypeface(mHeadlineFont);
-
-            card.findViewById(R.id.theme_preview_top_bar).setVisibility(View.VISIBLE);
-            TextView clock = card.findViewById(R.id.theme_preview_clock);
-            clock.setText(getFormattedTime());
-            clock.setTypeface(mHeadlineFont);
-
-            ViewGroup iconsContainer = card.findViewById(R.id.theme_preview_top_bar_icons);
-
-            for (int i = 0; i < iconsContainer.getChildCount() && i < mIcons.size(); i++) {
-                ((ImageView) iconsContainer.getChildAt(i))
-                        .setImageDrawable(mIcons.get(i).getConstantState().newDrawable().mutate());
-            }
-
-            ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
-
-            inflater.inflate(contentLayoutRes, body, true);
-
-            bindBody(false);
-
-            TextView editLabel = card.findViewById(R.id.edit_label);
-            editLabel.setOnClickListener(mEditClickListener);
-            card.setOnClickListener(mEditClickListener);
-            editLabel.setVisibility(mEditClickListener != null
-                    ? View.VISIBLE : View.INVISIBLE);
-            ColorStateList themeAccentColor = ColorStateList.valueOf(accentColor);
-            editLabel.setTextColor(themeAccentColor);
-            editLabel.setCompoundDrawableTintList(themeAccentColor);
-            View qsb = card.findViewById(R.id.theme_qsb);
-            if (qsb != null && qsb.getVisibility() == View.VISIBLE) {
-                if (qsb.getBackground() instanceof GradientDrawable) {
-                    GradientDrawable bg = (GradientDrawable) qsb.getBackground();
-                    float cornerRadius = useRoundedQSB(mCornerRadius)
-                            ? (float)qsb.getLayoutParams().height / 2 : mCornerRadius;
-                    bg.setCornerRadii(new float[]{
-                            cornerRadius, cornerRadius, cornerRadius, cornerRadius,
-                            cornerRadius, cornerRadius, cornerRadius, cornerRadius});
-                }
-            }
-        }
-
-        private boolean useRoundedQSB(int cornerRadius) {
-            return cornerRadius >=
-                    card.getResources().getDimensionPixelSize(R.dimen.roundCornerThreshold);
-        }
-
-        private String getFormattedTime() {
-            DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
-            StringBuffer time = new StringBuffer();
-            FieldPosition amPmPosition = new FieldPosition(DateFormat.Field.AM_PM);
-            df.format(Calendar.getInstance(TimeZone.getDefault()).getTime(), time, amPmPosition);
-            if (amPmPosition.getBeginIndex() > 0) {
-                time.delete(amPmPosition.getBeginIndex(), amPmPosition.getEndIndex());
-            }
-            return time.toString();
-        }
-
-        @Override
-        protected boolean containsWallpaper() {
-            return true;
-        }
-    }
-
     /**
      * Adapter class for mPreviewPager.
      * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
@@ -473,66 +324,9 @@
             addPage(new ThemeCoverPage(activity, theme.getTitle(),
                     previewInfo.resolveAccentColor(res), previewInfo.icons,
                     previewInfo.headlineFontFamily, previewInfo.bottomSheeetCornerRadius,
-                    editClickListener,
-                    new WallpaperPreviewLayoutListener(theme, previewInfo, true)) {
-
-                @Override
-                protected void bindBody(boolean forceRebind) {
-                    if (card == null) {
-                        return;
-                    }
-                    card.addOnLayoutChangeListener(coverCardLayoutListener);
-                    super.bindBody(forceRebind);
-
-                    // Color QS icons:
-                    int controlGreyColor = res.getColor(R.color.control_grey);
-                    ColorStateList tintList = new ColorStateList(
-                            new int[][]{
-                                    new int[]{android.R.attr.state_selected},
-                                    new int[]{android.R.attr.state_checked},
-                                    new int[]{-android.R.attr.state_enabled},
-                            },
-                            new int[] {
-                                    accentColor,
-                                    accentColor,
-                                    controlGreyColor
-                            }
-                    );
-
-                    for (int i = 0; i < mColorButtonIds.length; i++) {
-                        CompoundButton button = card.findViewById(mColorButtonIds[i]);
-                        if (button != null) {
-                            button.setButtonTintList(tintList);
-                        }
-                    }
-                    for (int i = 0; i < 3 && i < previewInfo.icons.size(); i++) {
-                        Drawable icon =
-                                previewInfo.icons.get(i).getConstantState().newDrawable().mutate();
-                        Drawable bgShape =
-                                previewInfo.shapeDrawable.getConstantState().newDrawable();
-                        bgShape.setTint(accentColor);
-
-                        ImageView bg = card.findViewById(mColorTileIds[i]);
-                        bg.setImageDrawable(bgShape);
-                        ImageView fg = card.findViewById(mColorTileIconIds[i]);
-                        fg.setImageDrawable(icon);
-                    }
-
-                    // Shape preview icons:
-
-                    for (int i = 0; i < 3 && i < previewInfo.shapeAppIcons.size(); i++) {
-                        ImageView iconView = card.findViewById(mShapeIconIds[i]);
-                        iconView.setBackground(
-                                previewInfo.shapeAppIcons.get(i));
-                    }
-
-                    Drawable background = card.findViewById(R.id.theme_preview_card_background)
-                            .getBackground();
-                    if (background != null) {
-                        background.setAlpha(128);
-                    }
-                }
-            });
+                    previewInfo.shapeDrawable, previewInfo.shapeAppIcons, editClickListener,
+                    mColorButtonIds, mColorTileIds, mColorTileIconIds, mShapeIconIds,
+                    new WallpaperPreviewLayoutListener(theme, previewInfo, true)));
             addPage(new ThemePreviewPage(activity, R.string.preview_name_font, R.drawable.ic_font,
                     R.layout.preview_card_font_content,
                     previewInfo.resolveAccentColor(res)) {
diff --git a/src/com/android/customization/picker/theme/ThemePreviewPage.java b/src/com/android/customization/picker/theme/ThemePreviewPage.java
new file mode 100644
index 0000000..73bd886
--- /dev/null
+++ b/src/com/android/customization/picker/theme/ThemePreviewPage.java
@@ -0,0 +1,238 @@
+package com.android.customization.picker.theme;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.icu.text.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StringRes;
+
+import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
+import com.android.wallpaper.R;
+
+import java.text.FieldPosition;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
+
+abstract class ThemePreviewPage extends PreviewPage {
+    @StringRes
+    final int nameResId;
+    @DrawableRes
+    final int iconSrc;
+    @LayoutRes
+    final int contentLayoutRes;
+    @ColorInt
+    final int accentColor;
+    protected final LayoutInflater inflater;
+
+    public ThemePreviewPage(Context context, @StringRes int titleResId,
+            @DrawableRes int iconSrc, @LayoutRes int contentLayoutRes,
+            @ColorInt int accentColor) {
+        super(null);
+        this.nameResId = titleResId;
+        this.iconSrc = iconSrc;
+        this.contentLayoutRes = contentLayoutRes;
+        this.accentColor = accentColor;
+        this.inflater = LayoutInflater.from(context);
+    }
+
+    @Override
+    public void bindPreviewContent() {
+        TextView header = card.findViewById(R.id.theme_preview_card_header);
+        header.setText(nameResId);
+        header.setCompoundDrawablesWithIntrinsicBounds(0, iconSrc, 0, 0);
+        header.setCompoundDrawableTintList(ColorStateList.valueOf(accentColor));
+        card.findViewById(R.id.theme_preview_top_bar).setVisibility(View.GONE);
+        card.findViewById(R.id.edit_label).setVisibility(View.GONE);
+
+        ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
+        inflater.inflate(contentLayoutRes, body, true);
+        bindBody(false);
+    }
+
+    protected boolean containsWallpaper() {
+        return false;
+    }
+
+    protected abstract void bindBody(boolean forceRebind);
+
+    static class ThemeCoverPage extends ThemePreviewPage {
+
+        private final Typeface mHeadlineFont;
+        private final List<Drawable> mIcons;
+        private final List<Drawable> mShapeAppIcons;
+        private Drawable mShapeDrawable;
+        private final int[] mColorButtonIds;
+        private final int[] mColorTileIds;
+        private final int[] mColorTileIconIds;
+        private final int[] mShapeIconIds;
+        private final Resources mRes;
+        private String mTitle;
+        private OnClickListener mEditClickListener;
+        private final OnLayoutChangeListener mListener;
+        private final int mCornerRadius;
+
+        public ThemeCoverPage(Context context, String title, int accentColor, List<Drawable> icons,
+                Typeface headlineFont, int cornerRadius,
+                Drawable shapeDrawable,
+                List<Drawable> shapeAppIcons,
+                OnClickListener editClickListener,
+                int[] colorButtonIds, int[] colorTileIds, int[] colorTileIconIds,
+                int[] shapeIconIds, OnLayoutChangeListener wallpaperListener) {
+            super(context, 0, 0, R.layout.preview_card_cover_content, accentColor);
+            mRes = context.getResources();
+            mTitle = title;
+            mHeadlineFont = headlineFont;
+            mIcons = icons;
+            mCornerRadius = cornerRadius;
+            mShapeDrawable = shapeDrawable;
+            mShapeAppIcons = shapeAppIcons;
+            mEditClickListener = editClickListener;
+            mColorButtonIds = colorButtonIds;
+            mColorTileIds = colorTileIds;
+            mColorTileIconIds = colorTileIconIds;
+            mShapeIconIds = shapeIconIds;
+            mListener = wallpaperListener;
+        }
+
+        @Override
+        protected void bindBody(boolean forceRebind) {
+            if (card == null) {
+                return;
+            }
+
+            card.addOnLayoutChangeListener(mListener);
+            if (forceRebind) {
+                card.requestLayout();
+            }
+
+            // Color QS icons:
+            int controlGreyColor = mRes.getColor(R.color.control_grey, null);
+            ColorStateList tintList = new ColorStateList(
+                    new int[][]{
+                            new int[]{android.R.attr.state_selected},
+                            new int[]{android.R.attr.state_checked},
+                            new int[]{-android.R.attr.state_enabled},
+                    },
+                    new int[] {
+                            accentColor,
+                            accentColor,
+                            controlGreyColor
+                    }
+            );
+
+            for (int i = 0; i < mColorButtonIds.length; i++) {
+                CompoundButton button = card.findViewById(mColorButtonIds[i]);
+                if (button != null) {
+                    button.setButtonTintList(tintList);
+                }
+            }
+            for (int i = 0; i < 3 && i < mIcons.size(); i++) {
+                Drawable icon =
+                        mIcons.get(i).getConstantState().newDrawable().mutate();
+                Drawable bgShape = mShapeDrawable.getConstantState().newDrawable();
+                bgShape.setTint(accentColor);
+
+                ImageView bg = card.findViewById(mColorTileIds[i]);
+                bg.setImageDrawable(bgShape);
+                ImageView fg = card.findViewById(mColorTileIconIds[i]);
+                fg.setImageDrawable(icon);
+            }
+
+            // Shape preview icons:
+
+            for (int i = 0; i < 3 && i < mShapeAppIcons.size(); i++) {
+                ImageView iconView = card.findViewById(mShapeIconIds[i]);
+                iconView.setBackground(mShapeAppIcons.get(i));
+            }
+
+            Drawable background = card.findViewById(R.id.theme_preview_card_background)
+                    .getBackground();
+            if (background != null) {
+                background.setAlpha(128);
+            }
+        }
+
+        @Override
+        public void bindPreviewContent() {
+            TextView header = card.findViewById(R.id.theme_preview_card_header);
+            header.setText(mTitle);
+            header.setTextAppearance(R.style.CoverTitleTextAppearance);
+            header.setTypeface(mHeadlineFont);
+
+            card.findViewById(R.id.theme_preview_top_bar).setVisibility(View.VISIBLE);
+            TextView clock = card.findViewById(R.id.theme_preview_clock);
+            clock.setText(getFormattedTime());
+            clock.setTypeface(mHeadlineFont);
+
+            ViewGroup iconsContainer = card.findViewById(R.id.theme_preview_top_bar_icons);
+
+            for (int i = 0; i < iconsContainer.getChildCount() && i < mIcons.size(); i++) {
+                ((ImageView) iconsContainer.getChildAt(i))
+                        .setImageDrawable(mIcons.get(i).getConstantState().newDrawable().mutate());
+            }
+
+            ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
+
+            inflater.inflate(contentLayoutRes, body, true);
+
+            bindBody(false);
+
+            TextView editLabel = card.findViewById(R.id.edit_label);
+            editLabel.setOnClickListener(mEditClickListener);
+            card.setOnClickListener(mEditClickListener);
+            editLabel.setVisibility(mEditClickListener != null
+                    ? View.VISIBLE : View.INVISIBLE);
+            ColorStateList themeAccentColor = ColorStateList.valueOf(accentColor);
+            editLabel.setTextColor(themeAccentColor);
+            editLabel.setCompoundDrawableTintList(themeAccentColor);
+            View qsb = card.findViewById(R.id.theme_qsb);
+            if (qsb != null && qsb.getVisibility() == View.VISIBLE) {
+                if (qsb.getBackground() instanceof GradientDrawable) {
+                    GradientDrawable bg = (GradientDrawable) qsb.getBackground();
+                    float cornerRadius = useRoundedQSB(mCornerRadius)
+                            ? (float)qsb.getLayoutParams().height / 2 : mCornerRadius;
+                    bg.setCornerRadii(new float[]{
+                            cornerRadius, cornerRadius, cornerRadius, cornerRadius,
+                            cornerRadius, cornerRadius, cornerRadius, cornerRadius});
+                }
+            }
+        }
+
+        private boolean useRoundedQSB(int cornerRadius) {
+            return cornerRadius >=
+                    card.getResources().getDimensionPixelSize(R.dimen.roundCornerThreshold);
+        }
+
+        private String getFormattedTime() {
+            DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
+            StringBuffer time = new StringBuffer();
+            FieldPosition amPmPosition = new FieldPosition(DateFormat.Field.AM_PM);
+            df.format(Calendar.getInstance(TimeZone.getDefault()).getTime(), time, amPmPosition);
+            if (amPmPosition.getBeginIndex() > 0) {
+                time.delete(amPmPosition.getBeginIndex(), amPmPosition.getEndIndex());
+            }
+            return time.toString();
+        }
+
+        @Override
+        protected boolean containsWallpaper() {
+            return true;
+        }
+    }
+}