Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 319aa0a..217b0ef 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -67,7 +67,7 @@
     <string name="option_previewed_description"><xliff:g name="style_name">%1$s</xliff:g>, currently previewed</string>
 
     <!-- Sample text used to show a preview of a selected font [CHAR LIMIT=3] -->
-    <string name="theme_font_example">ABC</string>
+    <string name="theme_font_example" translatable="false">ABC</string>
 
     <!-- Content description for previewing a style, describing each of their components. [CHAR_LIMIT=NONE] -->
     <string name="theme_description">Font: <xliff:g name="font_name">%1$s</xliff:g>, icons: <xliff:g name="icon_name">%2$s</xliff:g>, shape: <xliff:g name="shape_name">%3$s</xliff:g>, color: <xliff:g name="color_name">%4$s</xliff:g> </string>
@@ -105,6 +105,9 @@
     <!-- Message shown when a theme has been applied successfully in the system [CHAR LIMIT=NONE] -->
     <string name="applied_theme_msg">Style applied</string>
 
+    <!-- Message shown when a clock has been applied successfully in the system [CHAR LIMIT=NONE] -->
+    <string name="applied_clock_msg">Clock applied</string>
+
     <!-- Message shown when a theme couldn't be applied in the system because of an error
         [CHAR LIMIT=NONE] -->
     <string name="apply_theme_error_msg">There was a problem applying the style</string>
@@ -117,9 +120,10 @@
         [CHAR LIMIT=20] -->
     <string name="custom_theme_previous">Previous</string>
 
-    <!-- Label for a system Style/Theme (combination of fonts/colors/icons) that is defined and
-        customized by the user [CHAR LIMIT=15] -->
-    <string name="custom_theme_title">Custom</string>
+    <!-- Generic label for one system Style/Theme (combination of fonts/colors/icons) that is
+        defined and customized by the user (note there could be more than one so the label includes
+        a number, eg "Custom 1, Custom 2, etc") [CHAR LIMIT=15] -->
+    <string name="custom_theme_title">Custom <xliff:g name="custom_num" example="1">%1$d</xliff:g></string>
 
     <!-- Title for a screen that lets the user define their custom system Style/Theme
         [CHAR LIMIT=30] -->
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index ac32312..c1c6bbe 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -15,11 +15,7 @@
  */
 package com.android.customization.model.theme;
 
-import static com.android.customization.model.ResourceConstants.ACCENT_COLOR_DARK_NAME;
-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_CORNERRADIUS;
-import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
 import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
@@ -29,31 +25,22 @@
 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 static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.om.OverlayInfo;
-import android.content.om.OverlayManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
 import android.text.TextUtils;
 import android.util.Log;
 
-import androidx.annotation.Dimension;
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.ResourceConstants;
 import com.android.customization.model.ResourcesApkProvider;
 import com.android.customization.model.theme.ThemeBundle.Builder;
 import com.android.customization.model.theme.custom.CustomTheme;
@@ -64,6 +51,7 @@
 
 import com.bumptech.glide.request.RequestOptions;
 
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParserException;
@@ -74,7 +62,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * Default implementation of {@link ThemeBundleProvider} that reads Themes' overlays from a stub APK.
@@ -99,30 +86,17 @@
     private static final String WALLPAPER_ACTION_PREFIX = "theme_wallpaper_action_";
 
     private static final String DEFAULT_THEME_NAME= "default";
+    private static final String THEME_TITLE_FIELD = "_theme_title";
+    private static final String THEME_ID_FIELD = "_theme_id";
 
-    // List of packages
-    private final String[] mShapePreviewIconPackages;
+    private final OverlayThemeExtractor mOverlayProvider;
     private List<ThemeBundle> mThemes;
-    private Map<String, OverlayInfo> mOverlayInfos;
     private final CustomizationPreferences mCustomizationPreferences;
 
     public DefaultThemeProvider(Context context, CustomizationPreferences customizationPrefs) {
         super(context, context.getString(R.string.themes_stub_package));
-        OverlayManager om = context.getSystemService(OverlayManager.class);
+        mOverlayProvider = new OverlayThemeExtractor(context);
         mCustomizationPreferences = customizationPrefs;
-        mOverlayInfos = new HashMap<>();
-
-        Consumer<OverlayInfo> addToMap = overlayInfo -> mOverlayInfos.put(
-                overlayInfo.getPackageName(), overlayInfo);
-
-        UserHandle user = UserHandle.of(UserHandle.myUserId());
-        om.getOverlayInfosForTarget(ANDROID_PACKAGE, user).forEach(addToMap);
-        om.getOverlayInfosForTarget(SYSUI_PACKAGE, user).forEach(addToMap);
-        om.getOverlayInfosForTarget(SETTINGS_PACKAGE, user).forEach(addToMap);
-        om.getOverlayInfosForTarget(ResourceConstants.getLauncherPackage(context), user).forEach(addToMap);
-        om.getOverlayInfosForTarget(context.getPackageName(), user).forEach(addToMap);
-        mShapePreviewIconPackages = context.getResources().getStringArray(
-                R.array.icon_shape_preview_packages);
     }
 
     @Override
@@ -154,35 +128,36 @@
                                 "string", mStubPackageName)));
 
                 String shapeOverlayPackage = getOverlayPackage(SHAPE_PREFIX, themeName);
-                addShapeOverlay(builder, shapeOverlayPackage);
+                mOverlayProvider.addShapeOverlay(builder, shapeOverlayPackage);
 
                 String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, themeName);
-                addFontOverlay(builder, fontOverlayPackage);
+                mOverlayProvider.addFontOverlay(builder, fontOverlayPackage);
 
                 String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, themeName);
-                addColorOverlay(builder, colorOverlayPackage);
+                mOverlayProvider.addColorOverlay(builder, colorOverlayPackage);
 
                 String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX,
                         themeName);
 
-                addAndroidIconOverlay(builder, iconAndroidOverlayPackage);
+                mOverlayProvider.addAndroidIconOverlay(builder, iconAndroidOverlayPackage);
 
                 String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX, themeName);
 
-                addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
+                mOverlayProvider.addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
 
                 String iconLauncherOverlayPackage = getOverlayPackage(ICON_LAUNCHER_PREFIX,
                         themeName);
-                addNoPreviewIconOverlay(builder, iconLauncherOverlayPackage);
+                mOverlayProvider.addNoPreviewIconOverlay(builder, iconLauncherOverlayPackage);
 
                 String iconThemePickerOverlayPackage = getOverlayPackage(ICON_THEMEPICKER_PREFIX,
                         themeName);
-                addNoPreviewIconOverlay(builder, iconThemePickerOverlayPackage);
+                mOverlayProvider.addNoPreviewIconOverlay(builder,
+                        iconThemePickerOverlayPackage);
 
                 String iconSettingsOverlayPackage = getOverlayPackage(ICON_SETTINGS_PREFIX,
                         themeName);
 
-                addNoPreviewIconOverlay(builder, iconSettingsOverlayPackage);
+                mOverlayProvider.addNoPreviewIconOverlay(builder, iconSettingsOverlayPackage);
 
                 addWallpaper(themeName, builder);
 
@@ -193,7 +168,7 @@
             }
         }
 
-        addCustomTheme();
+        addCustomThemes();
     }
 
     private void addWallpaper(String themeName, Builder builder) {
@@ -235,9 +210,7 @@
                             LiveWallpaperInfo liveInfo = new LiveWallpaperInfo(wallpaperInfo);
                             builder.setLiveWallpaperInfo(liveInfo)
                                     .setWallpaperAsset(liveInfo.getThumbAsset(mContext));
-                        } catch (XmlPullParserException e) {
-                            Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
-                        } catch (IOException e) {
+                        } catch (XmlPullParserException | IOException e) {
                             Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
                         }
                     }
@@ -248,97 +221,6 @@
         }
     }
 
-    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))
-                    .setBottomSheetCornerRadius(loadDimen(CONFIG_CORNERRADIUS, shapeOverlayPackage))
-                    /*.setUseRoundIcon(loadBoolean(CONFIG_USE_ROUNDICON, shapeOverlayPackage))*/;
-        } else {
-            builder.setShapePath(mContext.getResources().getString(
-                    Resources.getSystem().getIdentifier(CONFIG_ICON_MASK, "string",
-                            ANDROID_PACKAGE)))
-                    .setBottomSheetCornerRadius(
-                            mContext.getResources().getDimensionPixelOffset(
-                                    Resources.getSystem().getIdentifier(CONFIG_CORNERRADIUS,
-                                            "dimen", ANDROID_PACKAGE)))
-                    /*.setUseRoundIcon(mContext.getResources().getBoolean(
-                            Resources.getSystem().getIdentifier(CONFIG_USE_ROUNDICON,
-                                    "boolean", 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),
-                    overlayPackage);
-        }
-    }
-
-    private void addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage)
-            throws NameNotFoundException {
-        if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
-            addIconOverlay(builder, iconSysUiOverlayPackage);
-        }
-    }
-
-    private void addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage)
-            throws NameNotFoundException {
-        if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) {
-            addIconOverlay(builder, iconAndroidOverlayPackage, ICONS_FOR_PREVIEW);
-        } else {
-            addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW);
-        }
-    }
-    private void addIconOverlay(Builder builder, String packageName, String... previewIcons)
-            throws NameNotFoundException {
-        builder.addOverlayPackage(getOverlayCategory(packageName), packageName);
-        for (String iconName : previewIcons) {
-            builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false));
-        }
-    }
-
-    private void addFontOverlay(Builder builder, String fontOverlayPackage)
-            throws NameNotFoundException {
-        if (!TextUtils.isEmpty(fontOverlayPackage)) {
-            builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
-                        fontOverlayPackage)
-                    .setBodyFontFamily(loadTypeface(
-                            ResourceConstants.CONFIG_BODY_FONT_FAMILY,
-                            fontOverlayPackage))
-                    .setHeadlineFontFamily(loadTypeface(
-                            ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
-                            fontOverlayPackage));
-        } else {
-            addSystemDefaultFont(builder);
-        }
-    }
-
     /**
      * Default theme requires different treatment: if there are overlay packages specified in the
      * stub apk, we'll use those, otherwise we'll get the System default values. But we cannot skip
@@ -358,37 +240,29 @@
         String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, DEFAULT_THEME_NAME);
 
         try {
-            builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage), colorOverlayPackage)
-                    .setColorAccentLight(loadColor(ACCENT_COLOR_LIGHT_NAME, colorOverlayPackage))
-                    .setColorAccentDark(loadColor(ACCENT_COLOR_DARK_NAME, colorOverlayPackage));
+            mOverlayProvider.addColorOverlay(builder, colorOverlayPackage);
         } catch (NameNotFoundException | NotFoundException e) {
-            Log.d(TAG, "Didn't find color overlay for default theme, will use system default", e);
-            addSystemDefaultColor(builder);
+            Log.d(TAG, "Didn't find color overlay for default theme, will use system default");
+            mOverlayProvider.addSystemDefaultColor(builder);
         }
 
         String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, DEFAULT_THEME_NAME);
 
         try {
-            builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage), fontOverlayPackage)
-                    .setBodyFontFamily(loadTypeface(ResourceConstants.CONFIG_BODY_FONT_FAMILY,
-                            fontOverlayPackage))
-                    .setHeadlineFontFamily(loadTypeface(
-                            ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
-                            fontOverlayPackage));
+            mOverlayProvider.addFontOverlay(builder, fontOverlayPackage);
         } catch (NameNotFoundException | NotFoundException e) {
-            Log.d(TAG, "Didn't find font overlay for default theme, will use system default", e);
-            addSystemDefaultFont(builder);
+            Log.d(TAG, "Didn't find font overlay for default theme, will use system default");
+            mOverlayProvider.addSystemDefaultFont(builder);
         }
 
         try {
             String shapeOverlayPackage = getOverlayPackage(SHAPE_PREFIX, DEFAULT_THEME_NAME);
-            builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage), shapeOverlayPackage)
-                    .setShapePath(loadString(CONFIG_ICON_MASK, shapeOverlayPackage));
+            mOverlayProvider.addShapeOverlay(builder ,shapeOverlayPackage, false);
         } catch (NameNotFoundException | NotFoundException e) {
-            Log.d(TAG, "Didn't find shape overlay for default theme, will use system default", e);
-            addSystemDefaultShape(builder);
+            Log.d(TAG, "Didn't find shape overlay for default theme, will use system default");
+            mOverlayProvider.addSystemDefaultShape(builder);
         }
-        for (String packageName : mShapePreviewIconPackages) {
+        for (String packageName : mOverlayProvider.getShapePreviewIconPackages()) {
             try {
                 builder.addShapePreviewIcon(
                         mContext.getPackageManager().getApplicationIcon(packageName));
@@ -401,24 +275,20 @@
         try {
             String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX,
                     DEFAULT_THEME_NAME);
-            builder.addOverlayPackage(getOverlayCategory(iconAndroidOverlayPackage),
-                        iconAndroidOverlayPackage)
-                    .addIcon(loadIconPreviewDrawable(ICON_ANDROID_PREFIX,
-                            iconAndroidOverlayPackage, false));
+            mOverlayProvider.addAndroidIconOverlay(builder, iconAndroidOverlayPackage);
         } catch (NameNotFoundException | NotFoundException e) {
-            Log.d(TAG, "Didn't find Android icons overlay for default theme, using system default",
-                    e);
-            addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW);
+            Log.d(TAG, "Didn't find Android icons overlay for default theme, using system default");
+            mOverlayProvider.addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW);
         }
 
         try {
             String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX,
                     DEFAULT_THEME_NAME);
-            addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
+            mOverlayProvider.addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
         } catch (NameNotFoundException | NotFoundException e) {
-            Log.d(TAG, "Didn't find SystemUi icons overlay for default theme, using system default",
-                    e);
-            addSystemDefaultIcons(builder, SYSUI_PACKAGE, ICONS_FOR_PREVIEW);
+            Log.d(TAG,
+                    "Didn't find SystemUi icons overlay for default theme, using system default");
+            mOverlayProvider.addSystemDefaultIcons(builder, SYSUI_PACKAGE, ICONS_FOR_PREVIEW);
         }
 
         addWallpaper(DEFAULT_THEME_NAME, builder);
@@ -426,84 +296,104 @@
         mThemes.add(builder.build(mContext));
     }
 
-    private void addSystemDefaultIcons(Builder builder, String packageName, String... previewIcons) {
-        try {
-            for (String iconName : previewIcons) {
-                builder.addIcon(loadIconPreviewDrawable(iconName, packageName, true));
-            }
-        } catch (NameNotFoundException | NotFoundException e) {
-            Log.w(TAG, "Didn't find android package icons, will skip preview", e);
+    @Override
+    public void storeCustomTheme(CustomTheme theme) {
+        if (mThemes == null) {
+            fetch(options -> {
+                addCustomThemeAndStore(theme);
+            }, false);
+        } else {
+            addCustomThemeAndStore(theme);
         }
     }
 
-    private void addSystemDefaultShape(Builder builder) {
-        Resources system = Resources.getSystem();
-        String iconMaskPath = system.getString(system.getIdentifier(CONFIG_ICON_MASK,
-                "string", ANDROID_PACKAGE));
-        builder.setShapePath(iconMaskPath)
-                .setBottomSheetCornerRadius(
-                        system.getDimensionPixelOffset(
-                        system.getIdentifier(CONFIG_CORNERRADIUS,
-                                "dimen", ANDROID_PACKAGE)));
+    private void addCustomThemeAndStore(CustomTheme theme) {
+        if (!mThemes.contains(theme)) {
+            mThemes.add(theme);
+        } else {
+            mThemes.replaceAll(t -> theme.equals(t) ? theme : t);
+        }
+        JSONArray themesArray = new JSONArray();
+        mThemes.stream()
+                .filter(themeBundle -> themeBundle instanceof CustomTheme
+                        && !themeBundle.getPackagesByCategory().isEmpty())
+                .forEachOrdered(themeBundle -> addThemeBundleToArray(themesArray, themeBundle));
+        mCustomizationPreferences.storeCustomThemes(themesArray.toString());
     }
 
-    private void addSystemDefaultColor(Builder builder) {
-        Resources system = Resources.getSystem();
-        int colorAccentLight = system.getColor(
-                system.getIdentifier(ACCENT_COLOR_LIGHT_NAME, "color", ANDROID_PACKAGE), null);
-        builder.setColorAccentLight(colorAccentLight);
-
-        int colorAccentDark = system.getColor(
-                system.getIdentifier(ACCENT_COLOR_DARK_NAME, "color", ANDROID_PACKAGE), null);
-        builder.setColorAccentDark(colorAccentDark);
-    }
-
-    private void addSystemDefaultFont(Builder builder) {
-        Resources system = Resources.getSystem();
-        String headlineFontFamily = system.getString(system.getIdentifier(
-                ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,"string", ANDROID_PACKAGE));
-        String bodyFontFamily = system.getString(system.getIdentifier(
-                ResourceConstants.CONFIG_BODY_FONT_FAMILY,
-                "string", ANDROID_PACKAGE));
-        builder.setHeadlineFontFamily(Typeface.create(headlineFontFamily, Typeface.NORMAL))
-                .setBodyFontFamily(Typeface.create(bodyFontFamily, Typeface.NORMAL));
-    }
-
-    @Override
-    public void storeCustomTheme(CustomTheme theme) {
-        mCustomizationPreferences.storeCustomTheme(theme.getSerializedPackages());
+    private void addThemeBundleToArray(JSONArray themesArray, ThemeBundle themeBundle) {
+        JSONObject jsonPackages = themeBundle.getJsonPackages();
+        try {
+            jsonPackages.put(THEME_TITLE_FIELD, themeBundle.getTitle());
+            if (themeBundle instanceof CustomTheme) {
+                jsonPackages.put(THEME_ID_FIELD, ((CustomTheme)themeBundle).getId());
+            }
+        } catch (JSONException e) {
+            Log.w("Exception saving theme's title", e);
+        }
+        themesArray.put(jsonPackages);
     }
 
     @Override
     public void removeCustomTheme(CustomTheme theme) {
-        //TODO: add support for multiple custom themes.
-        mCustomizationPreferences.storeCustomTheme("");
+        JSONArray themesArray = new JSONArray();
+        mThemes.stream()
+                .filter(themeBundle -> themeBundle instanceof CustomTheme
+                        && ((CustomTheme) themeBundle).isDefined())
+                .forEachOrdered(customTheme -> {
+                    if (!customTheme.equals(theme)) {
+                        addThemeBundleToArray(themesArray, customTheme);
+                    }
+                });
+        mCustomizationPreferences.storeCustomThemes(themesArray.toString());
     }
 
-    private void addCustomTheme() {
-        String serializedTheme = mCustomizationPreferences.getSerializedCustomTheme();
-        if (TextUtils.isEmpty(serializedTheme)) {
-            mThemes.add(new CustomTheme(mContext.getString(R.string.custom_theme_title),
-                    new HashMap<>(), null));
-            return;
+    private void addCustomThemes() {
+        String serializedThemes = mCustomizationPreferences.getSerializedCustomThemes();
+        int customThemesCount = 0;
+        if (!TextUtils.isEmpty(serializedThemes)) {
+            try {
+                JSONArray customThemes = new JSONArray(serializedThemes);
+                for (int i = 0; i < customThemes.length(); i++) {
+                    JSONObject jsonTheme = customThemes.getJSONObject(i);
+                    ThemeBundle.Builder builder = convertJsonToBuilder(jsonTheme);
+                    if (builder != null) {
+                        if (TextUtils.isEmpty(builder.getTitle())) {
+                            builder.setTitle(mContext.getString(R.string.custom_theme_title,
+                                    customThemesCount + 1));
+                        }
+                        mThemes.add(builder.build(mContext));
+                    } else {
+                        Log.w(TAG, "Couldn't read stored custom theme, resetting");
+                        mThemes.add(new CustomTheme(CustomTheme.newId(),
+                                mContext.getString(R.string.custom_theme_title,
+                                customThemesCount + 1), new HashMap<>(), null));
+                    }
+                    customThemesCount++;
+                }
+            } 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));
+            }
         }
-        ThemeBundle.Builder builder = parseCustomTheme(serializedTheme);
-        if (builder != null) {
-            builder.setTitle(mContext.getString(R.string.custom_theme_title));
-            mThemes.add(builder.build(mContext));
-        } else {
-            Log.w(TAG, "Couldn't read stored custom theme, resetting");
-            mThemes.add(new CustomTheme(mContext.getString(R.string.custom_theme_title),
-                    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));
+
     }
 
     @Override
-    public Builder parseCustomTheme(String serializedTheme) {
+    public CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException {
+        JSONObject theme = new JSONObject(serializedTheme);
+        return convertJsonToBuilder(theme);
+    }
+
+    @Nullable
+    private CustomTheme.Builder convertJsonToBuilder(JSONObject theme) throws JSONException {
         try {
             Map<String, String> customPackages = new HashMap<>();
-
-            JSONObject theme = new JSONObject(serializedTheme);
             Iterator<String> keysIterator = theme.keys();
 
             while (keysIterator.hasNext()) {
@@ -511,94 +401,35 @@
                 customPackages.put(category, theme.getString(category));
             }
             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));
-            addNoPreviewIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER));
-            addNoPreviewIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_THEMEPICKER));
-
+            mOverlayProvider.addShapeOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_SHAPE));
+            mOverlayProvider.addFontOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_FONT));
+            mOverlayProvider.addColorOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_COLOR));
+            mOverlayProvider.addAndroidIconOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_ICON_ANDROID));
+            mOverlayProvider.addSysUiIconOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_ICON_SYSUI));
+            mOverlayProvider.addNoPreviewIconOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_ICON_SETTINGS));
+            mOverlayProvider.addNoPreviewIconOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER));
+            mOverlayProvider.addNoPreviewIconOverlay(builder,
+                    customPackages.get(OVERLAY_CATEGORY_ICON_THEMEPICKER));
+            if (theme.has(THEME_TITLE_FIELD)) {
+                builder.setTitle(theme.getString(THEME_TITLE_FIELD));
+            }
+            if (theme.has(THEME_ID_FIELD)) {
+                builder.setId(theme.getString(THEME_ID_FIELD));
+            }
             return builder;
-        } catch (JSONException | NameNotFoundException | NotFoundException e) {
+        } catch (NameNotFoundException | NotFoundException e) {
             Log.i(TAG, "Couldn't parse serialized custom theme", e);
             return null;
         }
     }
 
-    private String getOverlayPackage(String prefix, String themeName) {
-        return getItemStringFromStub(prefix, themeName);
-    }
-
-    private ResourceAsset getDrawableResourceAsset(String prefix, String themeName) {
-        int drawableResId = mStubApkResources.getIdentifier(prefix + themeName,
-                "drawable", mStubPackageName);
-        return drawableResId == 0 ? null : new ResourceAsset(mStubApkResources, drawableResId,
-                RequestOptions.fitCenterTransform());
-    }
-
-    private Typeface loadTypeface(String configName, String fontOverlayPackage)
-            throws NameNotFoundException, NotFoundException {
-
-        // TODO(santie): check for font being present in system
-
-        Resources overlayRes = mContext.getPackageManager()
-                .getResourcesForApplication(fontOverlayPackage);
-
-        String fontFamily = overlayRes.getString(overlayRes.getIdentifier(configName,
-                "string", fontOverlayPackage));
-        return Typeface.create(fontFamily, Typeface.NORMAL);
-    }
-
-    private int loadColor(String colorName, String colorPackage)
-            throws NameNotFoundException, NotFoundException {
-
-        Resources overlayRes = mContext.getPackageManager()
-                .getResourcesForApplication(colorPackage);
-        return overlayRes.getColor(overlayRes.getIdentifier(colorName, "color", colorPackage),
-                null);
-    }
-
-    private String loadString(String stringName, String packageName)
-            throws NameNotFoundException, NotFoundException {
-
-        Resources overlayRes = mContext.getPackageManager().getResourcesForApplication(packageName);
-        return overlayRes.getString(overlayRes.getIdentifier(stringName, "string", packageName));
-    }
-
-    @Dimension
-    private int loadDimen(String dimenName, String packageName)
-            throws NameNotFoundException, NotFoundException {
-
-        Resources overlayRes = mContext.getPackageManager().getResourcesForApplication(packageName);
-        return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier(
-                dimenName, "dimen", packageName));
-    }
-
-    private boolean loadBoolean(String booleanName, String packageName)
-            throws NameNotFoundException, NotFoundException {
-
-        Resources overlayRes = mContext.getPackageManager().getResourcesForApplication(packageName);
-        return overlayRes.getBoolean(overlayRes.getIdentifier(
-                booleanName, "boolean", packageName));
-    }
-
-    private Drawable loadIconPreviewDrawable(String drawableName, String packageName,
-            boolean fromSystem) throws NameNotFoundException, NotFoundException {
-
-        Resources packageRes = mContext.getPackageManager().getResourcesForApplication(packageName);
-        Resources res = fromSystem ? Resources.getSystem() : packageRes;
-        return res.getDrawable(
-                packageRes.getIdentifier(drawableName, "drawable", packageName), null);
-    }
-
-    @Nullable
-    private String getOverlayCategory(String packageName) {
-       OverlayInfo info = mOverlayInfos.get(packageName);
-       return info != null ? info.getCategory() : null;
-    }
 
     @Override
     public ThemeBundle findEquivalent(ThemeBundle other) {
@@ -612,4 +443,15 @@
         }
         return null;
     }
+
+    private String getOverlayPackage(String prefix, String themeName) {
+        return getItemStringFromStub(prefix, themeName);
+    }
+
+    private ResourceAsset getDrawableResourceAsset(String prefix, String themeName) {
+        int drawableResId = mStubApkResources.getIdentifier(prefix + themeName,
+                "drawable", mStubPackageName);
+        return drawableResId == 0 ? null : new ResourceAsset(mStubApkResources, drawableResId,
+                RequestOptions.fitCenterTransform());
+    }
 }
diff --git a/src/com/android/customization/model/theme/OverlayThemeExtractor.java b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
new file mode 100644
index 0000000..3c261e7
--- /dev/null
+++ b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
@@ -0,0 +1,274 @@
+package com.android.customization.model.theme;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW;
+import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
+import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
+
+import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Dimension;
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.ThemeBundle.Builder;
+import com.android.wallpaper.R;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+class OverlayThemeExtractor {
+
+    private static final String TAG = "OverlayThemeExtractor";
+
+    private final Context mContext;
+    private final Map<String, OverlayInfo> mOverlayInfos = new HashMap<>();
+    // List of packages
+    private final String[] mShapePreviewIconPackages;
+
+    OverlayThemeExtractor(Context context) {
+        mContext = context;
+        OverlayManager om = context.getSystemService(OverlayManager.class);
+        Consumer<OverlayInfo> addToMap = overlayInfo -> mOverlayInfos.put(
+                overlayInfo.getPackageName(), overlayInfo);
+
+        UserHandle user = UserHandle.of(UserHandle.myUserId());
+        om.getOverlayInfosForTarget(ANDROID_PACKAGE, user).forEach(addToMap);
+        om.getOverlayInfosForTarget(SYSUI_PACKAGE, user).forEach(addToMap);
+        om.getOverlayInfosForTarget(SETTINGS_PACKAGE, user).forEach(addToMap);
+        om.getOverlayInfosForTarget(ResourceConstants.getLauncherPackage(context), user)
+                .forEach(addToMap);
+        om.getOverlayInfosForTarget(context.getPackageName(), user).forEach(addToMap);
+
+        mShapePreviewIconPackages = context.getResources().getStringArray(
+                R.array.icon_shape_preview_packages);
+    }
+
+    void addColorOverlay(Builder builder, String colorOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(colorOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage),
+                    colorOverlayPackage)
+                    .setColorAccentLight(loadColor(ResourceConstants.ACCENT_COLOR_LIGHT_NAME,
+                            colorOverlayPackage))
+                    .setColorAccentDark(loadColor(ResourceConstants.ACCENT_COLOR_DARK_NAME,
+                            colorOverlayPackage));
+        } else {
+            addSystemDefaultColor(builder);
+        }
+    }
+
+    void addShapeOverlay(Builder builder, String shapeOverlayPackage)
+            throws NameNotFoundException {
+        addShapeOverlay(builder, shapeOverlayPackage, true);
+    }
+
+    void addShapeOverlay(Builder builder, String shapeOverlayPackage, boolean addPreview)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(shapeOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage),
+                    shapeOverlayPackage)
+                    .setShapePath(
+                            loadString(ResourceConstants.CONFIG_ICON_MASK, shapeOverlayPackage))
+                    .setBottomSheetCornerRadius(
+                            loadDimen(ResourceConstants.CONFIG_CORNERRADIUS, shapeOverlayPackage));
+        } else {
+            addSystemDefaultShape(builder);
+        }
+        if (addPreview) {
+            addShapePreviewIcons(builder);
+        }
+    }
+
+    private void addShapePreviewIcons(Builder builder) {
+        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");
+            }
+        }
+    }
+
+    void addNoPreviewIconOverlay(Builder builder, String overlayPackage) {
+        if (!TextUtils.isEmpty(overlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(overlayPackage),
+                    overlayPackage);
+        }
+    }
+
+    void addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
+            addIconOverlay(builder, iconSysUiOverlayPackage);
+        }
+    }
+
+    void addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) {
+            addIconOverlay(builder, iconAndroidOverlayPackage, ICONS_FOR_PREVIEW);
+        } else {
+            addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW);
+        }
+    }
+
+    void addIconOverlay(Builder builder, String packageName, String... previewIcons)
+            throws NameNotFoundException {
+        builder.addOverlayPackage(getOverlayCategory(packageName), packageName);
+        for (String iconName : previewIcons) {
+            builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false));
+        }
+    }
+
+    void addFontOverlay(Builder builder, String fontOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(fontOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
+                    fontOverlayPackage)
+                    .setBodyFontFamily(loadTypeface(
+                            ResourceConstants.CONFIG_BODY_FONT_FAMILY,
+                            fontOverlayPackage))
+                    .setHeadlineFontFamily(loadTypeface(
+                            ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
+                            fontOverlayPackage));
+        } else {
+            addSystemDefaultFont(builder);
+        }
+    }
+
+    void addSystemDefaultIcons(Builder builder, String packageName,
+            String... previewIcons) {
+        try {
+            for (String iconName : previewIcons) {
+                builder.addIcon(loadIconPreviewDrawable(iconName, packageName, true));
+            }
+        } catch (NameNotFoundException | NotFoundException e) {
+            Log.w(TAG, "Didn't find android package icons, will skip preview", e);
+        }
+    }
+
+    void addSystemDefaultShape(Builder builder) {
+        Resources system = Resources.getSystem();
+        String iconMaskPath = system.getString(
+                system.getIdentifier(ResourceConstants.CONFIG_ICON_MASK,
+                        "string", ResourceConstants.ANDROID_PACKAGE));
+        builder.setShapePath(iconMaskPath)
+                .setBottomSheetCornerRadius(
+                        system.getDimensionPixelOffset(
+                                system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
+                                        "dimen", ResourceConstants.ANDROID_PACKAGE)));
+    }
+
+    void addSystemDefaultColor(Builder builder) {
+        Resources system = Resources.getSystem();
+        int colorAccentLight = system.getColor(
+                system.getIdentifier(ResourceConstants.ACCENT_COLOR_LIGHT_NAME, "color",
+                        ResourceConstants.ANDROID_PACKAGE), null);
+        builder.setColorAccentLight(colorAccentLight);
+
+        int colorAccentDark = system.getColor(
+                system.getIdentifier(ResourceConstants.ACCENT_COLOR_DARK_NAME, "color",
+                        ResourceConstants.ANDROID_PACKAGE), null);
+        builder.setColorAccentDark(colorAccentDark);
+    }
+
+    void addSystemDefaultFont(Builder builder) {
+        Resources system = Resources.getSystem();
+        String headlineFontFamily = system.getString(system.getIdentifier(
+                ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY, "string",
+                ResourceConstants.ANDROID_PACKAGE));
+        String bodyFontFamily = system.getString(system.getIdentifier(
+                ResourceConstants.CONFIG_BODY_FONT_FAMILY,
+                "string", ResourceConstants.ANDROID_PACKAGE));
+        builder.setHeadlineFontFamily(Typeface.create(headlineFontFamily, Typeface.NORMAL))
+                .setBodyFontFamily(Typeface.create(bodyFontFamily, Typeface.NORMAL));
+    }
+
+    Typeface loadTypeface(String configName, String fontOverlayPackage)
+            throws NameNotFoundException, NotFoundException {
+
+        // TODO(santie): check for font being present in system
+
+        Resources overlayRes = mContext.getPackageManager()
+                .getResourcesForApplication(fontOverlayPackage);
+
+        String fontFamily = overlayRes.getString(overlayRes.getIdentifier(configName,
+                "string", fontOverlayPackage));
+        return Typeface.create(fontFamily, Typeface.NORMAL);
+    }
+
+    int loadColor(String colorName, String colorPackage)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes = mContext.getPackageManager()
+                .getResourcesForApplication(colorPackage);
+        return overlayRes.getColor(overlayRes.getIdentifier(colorName, "color", colorPackage),
+                null);
+    }
+
+    String loadString(String stringName, String packageName)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        return overlayRes.getString(overlayRes.getIdentifier(stringName, "string", packageName));
+    }
+
+    @Dimension
+    int loadDimen(String dimenName, String packageName)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier(
+                dimenName, "dimen", packageName));
+    }
+
+    boolean loadBoolean(String booleanName, String packageName)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        return overlayRes.getBoolean(overlayRes.getIdentifier(
+                booleanName, "boolean", packageName));
+    }
+
+    Drawable loadIconPreviewDrawable(String drawableName, String packageName,
+            boolean fromSystem) throws NameNotFoundException, NotFoundException {
+
+        Resources packageRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        Resources res = fromSystem ? Resources.getSystem() : packageRes;
+        return res.getDrawable(
+                packageRes.getIdentifier(drawableName, "drawable", packageName), null);
+    }
+
+    @Nullable
+    String getOverlayCategory(String packageName) {
+        OverlayInfo info = mOverlayInfos.get(packageName);
+        return info != null ? info.getCategory() : null;
+    }
+
+    String[] getShapePreviewIconPackages() {
+        return mShapePreviewIconPackages;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index 414719e..9d2f397 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -192,10 +192,17 @@
         if (isDefault()) {
             return "";
         }
+        return getJsonPackages().toString();
+    }
+
+    JSONObject getJsonPackages() {
+        if (isDefault()) {
+            return new JSONObject();
+        }
         JSONObject json = new JSONObject(mPackagesByCategory);
         // Remove items with null values to avoid deserialization issues.
         removeNullValues(json);
-        return json.toString();
+        return json;
     }
 
     private void removeNullValues(JSONObject json) {
@@ -325,6 +332,10 @@
                     mWallpaperAsset, shapeIcons);
         }
 
+        public String getTitle() {
+            return mTitle;
+        }
+
         public Builder setTitle(String title) {
             mTitle = title;
             return this;
diff --git a/src/com/android/customization/model/theme/ThemeBundleProvider.java b/src/com/android/customization/model/theme/ThemeBundleProvider.java
index cd33a81..fac11cd 100644
--- a/src/com/android/customization/model/theme/ThemeBundleProvider.java
+++ b/src/com/android/customization/model/theme/ThemeBundleProvider.java
@@ -18,9 +18,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
-import com.android.customization.model.theme.ThemeBundle.Builder;
 import com.android.customization.model.theme.custom.CustomTheme;
 
+import org.json.JSONException;
 
 /**
  * Interface for a class that can retrieve Themes from the system.
@@ -43,7 +43,7 @@
 
     void removeCustomTheme(CustomTheme theme);
 
-    @Nullable Builder parseCustomTheme(String serializedTheme);
+    @Nullable CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException;
 
     ThemeBundle findEquivalent(ThemeBundle other);
 }
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index d873972..de6a98b 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) {
+        if (theme instanceof CustomTheme && !((CustomTheme) theme).isDefined()) {
             storeCustomTheme((CustomTheme) theme);
         }
         mCurrentOverlays = null;
diff --git a/src/com/android/customization/model/theme/custom/CustomTheme.java b/src/com/android/customization/model/theme/custom/CustomTheme.java
index 5e809a0..15e4eb8 100644
--- a/src/com/android/customization/model/theme/custom/CustomTheme.java
+++ b/src/com/android/customization/model/theme/custom/CustomTheme.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager;
@@ -25,12 +26,38 @@
 import com.android.wallpaper.R;
 
 import java.util.Map;
+import java.util.UUID;
 
 public class CustomTheme extends ThemeBundle {
 
-    public CustomTheme(String title, Map<String, String> overlayPackages,
+    public static String newId() {
+        return UUID.randomUUID().toString();
+    }
+
+    private final String mId;
+
+    public CustomTheme(@NonNull String id, String title, Map<String, String> overlayPackages,
             @Nullable PreviewInfo previewInfo) {
         super(title, overlayPackages, false, null, previewInfo);
+        mId = id;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof CustomTheme)) {
+            return false;
+        }
+        CustomTheme other = (CustomTheme) obj;
+        return mId.equals(other.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mId.hashCode();
     }
 
     @Override
@@ -60,9 +87,16 @@
     }
 
     public static class Builder extends ThemeBundle.Builder {
+        private String mId;
+
         @Override
         public CustomTheme build(Context context) {
-            return new CustomTheme(mTitle, mPackages, createPreviewInfo(context));
+            return new CustomTheme(mId, mTitle, mPackages, createPreviewInfo(context));
+        }
+
+        public Builder setId(String id) {
+            mId = id;
+            return this;
         }
     }
 }
diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
index f8e5fbb..0cc0161 100644
--- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java
+++ b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
@@ -16,6 +16,7 @@
 package com.android.customization.model.theme.custom;
 
 import android.content.Context;
+import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
 
@@ -31,7 +32,8 @@
     private final Map<String, String> mOverlayPackages = new HashMap<>();
     private final CustomTheme mOriginalTheme;
 
-    private CustomThemeManager(Map<String, String> overlayPackages, @Nullable CustomTheme originalTheme) {
+    private CustomThemeManager(Map<String, String> overlayPackages,
+            @Nullable CustomTheme originalTheme) {
         mOverlayPackages.putAll(overlayPackages);
         mOriginalTheme = originalTheme;
     }
@@ -43,7 +45,11 @@
 
     @Override
     public void apply(ThemeComponentOption option, @Nullable Callback callback) {
-        mOverlayPackages.putAll(option.getOverlayPackages());
+        option.getOverlayPackages().forEach((category, packageName) -> {
+            if (!TextUtils.isEmpty(packageName)) {
+                mOverlayPackages.put(category, packageName);
+            }
+        });
         if (callback != null) {
             callback.onSuccess();
         }
@@ -53,10 +59,8 @@
         return mOverlayPackages;
     }
 
-    public CustomTheme buildPartialCustomTheme(Context context) {
-        return new CustomTheme(mOriginalTheme != null
-                ? mOriginalTheme.getTitle() : context.getString(R.string.custom_theme_title),
-                mOverlayPackages, null);
+    public CustomTheme buildPartialCustomTheme(String id, String title) {
+        return new CustomTheme(id, title, mOverlayPackages, null);
     }
 
     @Override
@@ -74,6 +78,6 @@
             return new CustomThemeManager(customTheme.getPackagesByCategory(), customTheme);
         }
         // Seed the first custom theme with the currently applied theme.
-        return new CustomThemeManager(themeManager.getCurrentOverlays(), null);
+        return new CustomThemeManager(themeManager.getCurrentOverlays(), customTheme);
     }
 }
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index fa45931..2dc163e 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -150,7 +150,8 @@
         @Override
         public void bindThumbnailTile(View view) {
             Resources res = view.getContext().getResources();
-            Drawable icon = mIcons.get(THUMBNAIL_ICON_POSITION).mutate();
+            Drawable icon = mIcons.get(THUMBNAIL_ICON_POSITION)
+                    .getConstantState().newDrawable().mutate();
             icon.setTint(res.getColor(R.color.icon_thumbnail_color, null));
             ((ImageView) view.findViewById(R.id.option_icon)).setImageDrawable(
                     icon);
diff --git a/src/com/android/customization/module/CustomizationPreferences.java b/src/com/android/customization/module/CustomizationPreferences.java
index dc7662c..d32143a 100644
--- a/src/com/android/customization/module/CustomizationPreferences.java
+++ b/src/com/android/customization/module/CustomizationPreferences.java
@@ -22,9 +22,9 @@
     String KEY_CUSTOM_THEME= "themepicker_custom_theme";
     String KEY_VISITED_PREFIX = "themepicker_visited_";
 
-    String getSerializedCustomTheme();
+    String getSerializedCustomThemes();
 
-    void storeCustomTheme(String serializedCustomTheme);
+    void storeCustomThemes(String serializedCustomThemes);
 
     boolean getTabVisited(String id);
 
diff --git a/src/com/android/customization/module/DefaultCustomizationPreferences.java b/src/com/android/customization/module/DefaultCustomizationPreferences.java
index 202ff49..e6b4d05 100644
--- a/src/com/android/customization/module/DefaultCustomizationPreferences.java
+++ b/src/com/android/customization/module/DefaultCustomizationPreferences.java
@@ -28,13 +28,13 @@
 
 
     @Override
-    public String getSerializedCustomTheme() {
+    public String getSerializedCustomThemes() {
         return mSharedPrefs.getString(KEY_CUSTOM_THEME, null);
     }
 
     @Override
-    public void storeCustomTheme(String serializedCustomTheme) {
-        mSharedPrefs.edit().putString(KEY_CUSTOM_THEME, serializedCustomTheme).apply();
+    public void storeCustomThemes(String serializedCustomThemes) {
+        mSharedPrefs.edit().putString(KEY_CUSTOM_THEME, serializedCustomThemes).apply();
     }
 
     @Override
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index c20b73f..281ff78 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -141,6 +141,18 @@
         mUserEventLogger.logResumed();
         // refresh the sections as the preview may have changed
         initSections();
+        if (mBottomNav == null) {
+            return;
+        }
+        CustomizationSection section = mSections.get(mBottomNav.getSelectedItemId());
+        if (section == null) {
+            return;
+        }
+        // Keep CategoryFragment's design to load category within its fragment
+        if (section instanceof WallpaperSection) {
+            switchFragment(section);
+            section.onVisible();
+        }
     }
 
     @Override
@@ -266,6 +278,17 @@
         }
     }
 
+    @Override
+    public void onBackPressed() {
+        if (getSupportFragmentManager().popBackStackImmediate()) {
+            return;
+        }
+        if (moveTaskToBack(false)) {
+            return;
+        }
+        super.onBackPressed();
+    }
+
     private void navigateToSection(@IdRes int id) {
         mBottomNav.setSelectedItemId(id);
     }
diff --git a/src/com/android/customization/picker/clock/ClockFragment.java b/src/com/android/customization/picker/clock/ClockFragment.java
index 73a8af4..c3e1c97 100644
--- a/src/com/android/customization/picker/clock/ClockFragment.java
+++ b/src/com/android/customization/picker/clock/ClockFragment.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -87,7 +88,9 @@
             mClockManager.apply(mSelectedOption, new Callback() {
                 @Override
                 public void onSuccess() {
-                    getActivity().finish();
+                    mOptionsController.setAppliedOption(mSelectedOption);
+                    Toast.makeText(getContext(), R.string.applied_clock_msg,
+                            Toast.LENGTH_SHORT).show();
                 }
 
                 @Override
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 40d1fe8..0ea9617 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -55,11 +56,14 @@
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.WallpaperSetter;
 
+import org.json.JSONException;
+
 import java.util.ArrayList;
 import java.util.List;
 
 public class CustomThemeActivity extends FragmentActivity implements
         CustomThemeComponentFragmentHost {
+    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";
     public static final int REQUEST_CODE_CUSTOM_THEME = 1;
@@ -84,15 +88,20 @@
         Intent intent = getIntent();
         CustomTheme customTheme = null;
         if (intent != null && intent.hasExtra(EXTRA_THEME_PACKAGES)
-                && intent.hasExtra(EXTRA_THEME_TITLE)) {
+                && intent.hasExtra(EXTRA_THEME_TITLE) && intent.hasExtra(EXTRA_THEME_ID)) {
             ThemeBundleProvider themeProvider =
                     new DefaultThemeProvider(this, injector.getCustomizationPreferences(this));
-            Builder themeBuilder = themeProvider.parseCustomTheme(
-                    intent.getStringExtra(EXTRA_THEME_PACKAGES));
-            if (themeBuilder != null) {
-                themeBuilder.setTitle(intent.getStringExtra(EXTRA_THEME_TITLE));
+            try {
+                CustomTheme.Builder themeBuilder = themeProvider.parseCustomTheme(
+                        intent.getStringExtra(EXTRA_THEME_PACKAGES));
+                if (themeBuilder != null) {
+                    themeBuilder.setId(intent.getStringExtra(EXTRA_THEME_ID));
+                    themeBuilder.setTitle(intent.getStringExtra(EXTRA_THEME_TITLE));
+                    customTheme = themeBuilder.build(this);
+                }
+            } catch (JSONException e) {
+                Log.w(TAG, "Couldn't parse provided custom theme, will override it");
             }
-            customTheme = (CustomTheme) themeBuilder.build(this);
         }
 
         mThemeManager = new ThemeManager(
@@ -135,7 +144,7 @@
     private void navigateToStep(int i) {
         FragmentManager fragmentManager = getSupportFragmentManager();
         ComponentStep step = mSteps.get(i);
-        Fragment fragment = step.getFragment();
+        Fragment fragment = step.getFragment(mCustomThemeManager.getOriginalTheme().getTitle());
 
         FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
         fragmentTransaction.replace(R.id.fragment_container, fragment);
@@ -145,7 +154,7 @@
         }
         fragmentTransaction.commit();
         fragmentManager.executePendingTransactions();
-        updateApplyButtonLabel();
+        updateNavigationButtonLabels();
     }
 
     private void initSteps(int currentStep) {
@@ -166,15 +175,16 @@
                 if (mCurrentStep < mSteps.size() - 1) {
                     navigateToStep(mCurrentStep + 1);
                 } else {
+                    CustomTheme originalTheme = mCustomThemeManager.getOriginalTheme();
+
                     // We're on the last step, apply theme and leave
                     CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(
-                            CustomThemeActivity.this);
+                            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 = (mCustomThemeManager.getOriginalTheme() != null
-                            && mCustomThemeManager.getOriginalTheme().isEquivalent(themeToApply))
+                    ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply))
                                 ? null : mThemeManager.findThemeByPackages(themeToApply);
 
                     if (equivalent != null) {
@@ -233,10 +243,11 @@
     @Override
     public void setCurrentStep(int i) {
         mCurrentStep = i;
-        updateApplyButtonLabel();
+        updateNavigationButtonLabels();
     }
 
-    private void updateApplyButtonLabel() {
+    private void updateNavigationButtonLabels() {
+        mPreviousButton.setVisibility(mCurrentStep == 0 ? View.INVISIBLE : View.VISIBLE);
         mNextButton.setText((mCurrentStep < mSteps.size() -1) ? R.string.custom_theme_next
                 : R.string.apply_btn);
     }
@@ -282,9 +293,9 @@
             this.position = position;
         }
 
-        CustomThemeComponentFragment getFragment() {
+        CustomThemeComponentFragment getFragment(String title) {
             if (mFragment == null) {
-                mFragment = createFragment();
+                mFragment = createFragment(title);
             }
             return mFragment;
         }
@@ -292,7 +303,7 @@
         /**
          * @return a newly created fragment that will handle this step's UI.
          */
-        abstract CustomThemeComponentFragment createFragment();
+        abstract CustomThemeComponentFragment createFragment(String title);
     }
 
     private class FontStep extends ComponentStep<FontOption> {
@@ -303,9 +314,9 @@
         }
 
         @Override
-        CustomThemeComponentFragment createFragment() {
+        CustomThemeComponentFragment createFragment(String title) {
             return CustomThemeComponentFragment.newInstance(
-                    CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
+                    title,
                     position,
                     titleResId);
         }
@@ -319,9 +330,9 @@
         }
 
         @Override
-        CustomThemeComponentFragment createFragment() {
+        CustomThemeComponentFragment createFragment(String title) {
             return CustomThemeComponentFragment.newInstance(
-                    CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
+                    title,
                     position,
                     titleResId);
         }
@@ -335,9 +346,9 @@
         }
 
         @Override
-        CustomThemeComponentFragment createFragment() {
+        CustomThemeComponentFragment createFragment(String title) {
             return CustomThemeComponentFragment.newInstance(
-                    CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
+                    title,
                     position,
                     titleResId,
                     true);
@@ -352,9 +363,9 @@
         }
 
         @Override
-        CustomThemeComponentFragment createFragment() {
+        CustomThemeComponentFragment createFragment(String title) {
             return CustomThemeComponentFragment.newInstance(
-                    CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
+                    title,
                     position,
                     titleResId);
         }
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index e3695e4..5b590f0 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -234,7 +234,7 @@
             mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
             mOptionsController.addListener(selected -> {
                 if (selected instanceof CustomTheme && !((CustomTheme) selected).isDefined()) {
-                    navigateToCustomTheme(null);
+                    navigateToCustomTheme((CustomTheme) selected);
                 } else {
                     mSelectedTheme = (ThemeBundle) selected;
                     if (mUseMyWallpaper || mSelectedTheme instanceof CustomTheme) {
@@ -292,13 +292,12 @@
         }, true);
     }
 
-    private void navigateToCustomTheme(@Nullable CustomTheme themeToEdit) {
+    private void navigateToCustomTheme(CustomTheme themeToEdit) {
         Intent intent = new Intent(getActivity(), CustomThemeActivity.class);
-        if (themeToEdit != null) {
-            intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle());
-            intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES,
-                    themeToEdit.getSerializedPackages());
-        }
+        intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle());
+        intent.putExtra(CustomThemeActivity.EXTRA_THEME_ID, themeToEdit.getId());
+        intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES,
+                themeToEdit.getSerializedPackages());
         startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME);
     }
 
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index 9725995..de2f09c 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -111,7 +111,12 @@
         if (!mOptions.contains(option)) {
             throw new IllegalArgumentException("Invalid option");
         }
+        CustomizationOption lastAppliedOption = mAppliedOption;
         mAppliedOption = option;
+        mAdapter.notifyItemChanged(mOptions.indexOf(option));
+        if (lastAppliedOption != null) {
+            mAdapter.notifyItemChanged(mOptions.indexOf(lastAppliedOption));
+        }
     }
 
     private void updateActivatedStatus(CustomizationOption option, boolean isActivated) {
@@ -125,23 +130,26 @@
 
             if (holder instanceof TileViewHolder) {
                 TileViewHolder tileHolder = (TileViewHolder) holder;
-                if (isActivated) {
-                    if (option == mAppliedOption) {
+                if (tileHolder.labelView != null) {
+                    if (isActivated) {
+                        if (option == mAppliedOption) {
+                            CharSequence cd = mContainer.getContext().getString(
+                                    R.string.option_applied_previewed_description,
+                                    option.getTitle());
+                            tileHolder.labelView.setContentDescription(cd);
+                        } else {
+                            CharSequence cd = mContainer.getContext().getString(
+                                    R.string.option_previewed_description, option.getTitle());
+                            tileHolder.labelView.setContentDescription(cd);
+                        }
+                    } else if (option == mAppliedOption) {
                         CharSequence cd = mContainer.getContext().getString(
-                                R.string.option_applied_previewed_description, option.getTitle());
+                                R.string.option_applied_description, option.getTitle());
                         tileHolder.labelView.setContentDescription(cd);
                     } else {
-                        CharSequence cd = mContainer.getContext().getString(
-                                R.string.option_previewed_description, option.getTitle());
-                        tileHolder.labelView.setContentDescription(cd);
+                        // Remove content description
+                        tileHolder.labelView.setContentDescription(null);
                     }
-                } else if (option == mAppliedOption) {
-                    CharSequence cd = mContainer.getContext().getString(
-                            R.string.option_applied_description, option.getTitle());
-                    tileHolder.labelView.setContentDescription(cd);
-                } else {
-                    // Remove content description
-                    tileHolder.labelView.setContentDescription(null);
                 }
             }
         }