Merge "Theme picker polish fixes." into ub-launcher3-qt-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8f1ecdd..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>
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index a4f9a31..c1c6bbe 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -86,6 +86,8 @@
     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";
 
     private final OverlayThemeExtractor mOverlayProvider;
     private List<ThemeBundle> mThemes;
@@ -306,23 +308,41 @@
     }
 
     private void addCustomThemeAndStore(CustomTheme theme) {
-        mThemes.add(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 -> themesArray.put(themeBundle.getJsonPackages()));
+                .forEachOrdered(themeBundle -> addThemeBundleToArray(themesArray, themeBundle));
         mCustomizationPreferences.storeCustomThemes(themesArray.toString());
     }
 
+    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) {
         JSONArray themesArray = new JSONArray();
         mThemes.stream()
-                .filter(themeBundle -> themeBundle instanceof CustomTheme)
-                .forEachOrdered(themeBundle -> {
-                    if (!themeBundle.equals(theme)) {
-                        themesArray.put(themeBundle.getJsonPackages());
+                .filter(themeBundle -> themeBundle instanceof CustomTheme
+                        && ((CustomTheme) themeBundle).isDefined())
+                .forEachOrdered(customTheme -> {
+                    if (!customTheme.equals(theme)) {
+                        addThemeBundleToArray(themesArray, customTheme);
                     }
                 });
         mCustomizationPreferences.storeCustomThemes(themesArray.toString());
@@ -335,40 +355,43 @@
             try {
                 JSONArray customThemes = new JSONArray(serializedThemes);
                 for (int i = 0; i < customThemes.length(); i++) {
-                    ThemeBundle.Builder builder = convertJsonToBuilder(
-                            customThemes.getJSONObject(i));
+                    JSONObject jsonTheme = customThemes.getJSONObject(i);
+                    ThemeBundle.Builder builder = convertJsonToBuilder(jsonTheme);
                     if (builder != null) {
-                        builder.setTitle(mContext.getString(R.string.custom_theme_title,
-                                customThemesCount + 1));
+                        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(mContext.getString(R.string.custom_theme_title,
+                        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(mContext.getString(R.string.custom_theme_title,
+                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(mContext.getString(R.string.custom_theme_title,
+        mThemes.add(new CustomTheme(CustomTheme.newId(), mContext.getString(R.string.custom_theme_title,
                 customThemesCount + 1), new HashMap<>(), null));
 
     }
 
     @Override
-    public Builder parseCustomTheme(String serializedTheme) throws JSONException {
+    public CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException {
         JSONObject theme = new JSONObject(serializedTheme);
         return convertJsonToBuilder(theme);
     }
 
     @Nullable
-    private Builder convertJsonToBuilder(JSONObject theme) throws JSONException {
+    private CustomTheme.Builder convertJsonToBuilder(JSONObject theme) throws JSONException {
         try {
             Map<String, String> customPackages = new HashMap<>();
             Iterator<String> keysIterator = theme.keys();
@@ -394,7 +417,12 @@
                     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 (NameNotFoundException | NotFoundException e) {
             Log.i(TAG, "Couldn't parse serialized custom theme", e);
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index ad21405..9d2f397 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -332,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 578b96c..fac11cd 100644
--- a/src/com/android/customization/model/theme/ThemeBundleProvider.java
+++ b/src/com/android/customization/model/theme/ThemeBundleProvider.java
@@ -18,7 +18,6 @@
 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;
@@ -44,7 +43,7 @@
 
     void removeCustomTheme(CustomTheme theme);
 
-    @Nullable Builder parseCustomTheme(String serializedTheme) throws JSONException;
+    @Nullable CustomTheme.Builder parseCustomTheme(String serializedTheme) throws JSONException;
 
     ThemeBundle findEquivalent(ThemeBundle other);
 }
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 5463eb0..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;
 
@@ -44,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();
         }
@@ -54,8 +59,8 @@
         return mOverlayPackages;
     }
 
-    public CustomTheme buildPartialCustomTheme(String title) {
-        return new CustomTheme(title, mOverlayPackages, null);
+    public CustomTheme buildPartialCustomTheme(String id, String title) {
+        return new CustomTheme(id, title, mOverlayPackages, null);
     }
 
     @Override
@@ -73,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/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 8cccd54..0ea9617 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -63,6 +63,7 @@
 
 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;
@@ -87,16 +88,16 @@
         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 = null;
             try {
-                themeBuilder = themeProvider.parseCustomTheme(
+                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 = (CustomTheme) themeBuilder.build(this);
+                    customTheme = themeBuilder.build(this);
                 }
             } catch (JSONException e) {
                 Log.w(TAG, "Couldn't parse provided custom theme, will override it");
@@ -143,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);
@@ -178,16 +179,12 @@
 
                     // We're on the last step, apply theme and leave
                     CustomTheme themeToApply = mCustomThemeManager.buildPartialCustomTheme(
-                            originalTheme != null
-                                    ? originalTheme.getTitle()
-                                    : getString(R.string.custom_theme_title,
-                                            0));
+                            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 != null
-                            && originalTheme.isEquivalent(themeToApply))
+                    ThemeBundle equivalent = (originalTheme.isEquivalent(themeToApply))
                                 ? null : mThemeManager.findThemeByPackages(themeToApply);
 
                     if (equivalent != null) {
@@ -296,9 +293,9 @@
             this.position = position;
         }
 
-        CustomThemeComponentFragment getFragment() {
+        CustomThemeComponentFragment getFragment(String title) {
             if (mFragment == null) {
-                mFragment = createFragment();
+                mFragment = createFragment(title);
             }
             return mFragment;
         }
@@ -306,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> {
@@ -317,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);
         }
@@ -333,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);
         }
@@ -349,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);
@@ -366,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);
     }