Custom Theme 7/n: delete custom theme

Add delete option on custom theme fragments, and reload the
options when going back after deleting to ensure we show the
correct ones.

Bug: 124796742
Change-Id: I23fa0f418a3872a5426faa4b7d514bc0395f79b5
diff --git a/res/drawable/ic_close_24px.xml b/res/drawable/ic_close_24px.xml
new file mode 100644
index 0000000..ff1d2d5
--- /dev/null
+++ b/res/drawable/ic_close_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z"/>
+</vector>
diff --git a/res/drawable/ic_delete_24px.xml b/res/drawable/ic_delete_24px.xml
new file mode 100644
index 0000000..f82e18d
--- /dev/null
+++ b/res/drawable/ic_delete_24px.xml
@@ -0,0 +1,30 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V6h1V4H15zM17,19H7V6h10V19z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M9,8h2v9h-2z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M13,8h2v9h-2z"/>
+</vector>
diff --git a/res/menu/custom_theme_editor_menu.xml b/res/menu/custom_theme_editor_menu.xml
new file mode 100644
index 0000000..ac02606
--- /dev/null
+++ b/res/menu/custom_theme_editor_menu.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/custom_theme_delete"
+        android:title="@string/custom_theme_delete"
+        android:icon="@drawable/ic_delete_24px"
+        app:showAsAction="always"/>
+</menu>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 46d3362..ca56f83 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -109,6 +109,10 @@
         [CHAR LIMIT=30] -->
     <string name="custom_theme_fragment_title">Custom Style</string>
 
+    <!-- Title of a menu option that removes the custom theme being currently edited
+        [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 -->
     <string name="font_component_title">Choose font</string>
 
@@ -124,4 +128,14 @@
     <!-- Text explaining what the current step is in setting a custom theme, eg: "1 of 4".
     [CHAR_LIMIT=40] -->
     <string name="component_step_counter"><xliff:g name="current_step" example="1">%1$d</xliff:g> of <xliff:g name="total_steps" example="4">%2$d</xliff:g></string>
+
+    <!-- Dialog box asking the user to confirm if they want to delete the current custom style
+        [CHAR_LIMIT=NONE] -->
+    <string name="delete_custom_theme_confirmation">Delete Custom style?</string>
+
+    <!-- Button in a dialog box confirming that the current style will be deleted [CHAR_LIMIT=20] -->
+    <string name="delete_custom_theme_button">Delete</string>
+
+    <!-- Generic label for canceling the current action [CHAR_LIMIT=20] -->
+    <string name="cancel">Cancel</string>
 </resources>
diff --git a/robolectric_tests/src/com/android/customization/model/grid/GridOptionsManagerTest.java b/robolectric_tests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
index abbbaa0..154e93d 100644
--- a/robolectric_tests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
+++ b/robolectric_tests/src/com/android/customization/model/grid/GridOptionsManagerTest.java
@@ -66,7 +66,7 @@
 
     @Test
     public void testFetch_backgroundThread() {
-        mManager.fetchOptions(null);
+        mManager.fetchOptions(null, false);
         Robolectric.flushBackgroundThreadScheduler();
         verify(mProvider).fetch(anyBoolean());
     }
diff --git a/src/com/android/customization/model/CustomizationManager.java b/src/com/android/customization/model/CustomizationManager.java
index 3f75be5..3296ca1 100644
--- a/src/com/android/customization/model/CustomizationManager.java
+++ b/src/com/android/customization/model/CustomizationManager.java
@@ -65,5 +65,5 @@
      * Loads the available options for the type of Customization managed by this class, calling the
      * given callback when done.
      */
-    void fetchOptions(OptionsFetchedListener<T> callback);
+    void fetchOptions(OptionsFetchedListener<T> callback, boolean reload);
 }
diff --git a/src/com/android/customization/model/clock/ClockManager.java b/src/com/android/customization/model/clock/ClockManager.java
index 13c3530..411a536 100644
--- a/src/com/android/customization/model/clock/ClockManager.java
+++ b/src/com/android/customization/model/clock/ClockManager.java
@@ -49,7 +49,7 @@
     }
 
     @Override
-    public void fetchOptions(OptionsFetchedListener<Clockface> callback) {
+    public void fetchOptions(OptionsFetchedListener<Clockface> callback, boolean reload) {
         mClockProvider.fetch(callback, false);
     }
 
diff --git a/src/com/android/customization/model/grid/GridOptionsManager.java b/src/com/android/customization/model/grid/GridOptionsManager.java
index b411e10..441994e 100644
--- a/src/com/android/customization/model/grid/GridOptionsManager.java
+++ b/src/com/android/customization/model/grid/GridOptionsManager.java
@@ -51,7 +51,7 @@
     }
 
     @Override
-    public void fetchOptions(OptionsFetchedListener<GridOption> callback) {
+    public void fetchOptions(OptionsFetchedListener<GridOption> callback, boolean reload) {
         new FetchTask(mProvider, callback).execute();
     }
 
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 549f04f..28af9ea 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -409,6 +409,12 @@
         mCustomizationPreferences.storeCustomTheme(theme.getSerializedPackages());
     }
 
+    @Override
+    public void removeCustomTheme(CustomTheme theme) {
+        //TODO: add support for multiple custom themes.
+        mCustomizationPreferences.storeCustomTheme("");
+    }
+
     private void addCustomTheme() {
         String serializedTheme = mCustomizationPreferences.getSerializedCustomTheme();
         if (TextUtils.isEmpty(serializedTheme)) {
diff --git a/src/com/android/customization/model/theme/ThemeBundleProvider.java b/src/com/android/customization/model/theme/ThemeBundleProvider.java
index f2e1915..7a7ba28 100644
--- a/src/com/android/customization/model/theme/ThemeBundleProvider.java
+++ b/src/com/android/customization/model/theme/ThemeBundleProvider.java
@@ -41,5 +41,7 @@
 
     void storeCustomTheme(CustomTheme theme);
 
+    void removeCustomTheme(CustomTheme theme);
+
     @Nullable Builder parseCustomTheme(String serializedTheme);
 }
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index ee8a6ef..439d560 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -158,8 +158,8 @@
     }
 
     @Override
-    public void fetchOptions(OptionsFetchedListener<ThemeBundle> callback) {
-        mProvider.fetch(callback, false);
+    public void fetchOptions(OptionsFetchedListener<ThemeBundle> callback, boolean reload) {
+        mProvider.fetch(callback, reload);
     }
 
     private boolean disableCurrentOverlay(String targetPackage, String category) {
@@ -185,4 +185,8 @@
         return Settings.Secure.getString(mActivity.getContentResolver(),
                 ResourceConstants.THEME_SETTING);
     }
+
+    public void removeCustomTheme(CustomTheme theme) {
+        mProvider.removeCustomTheme(theme);
+    }
 }
diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
index 1613199..0ce5954 100644
--- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java
+++ b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
@@ -28,10 +28,14 @@
 public class CustomThemeManager implements CustomizationManager<ThemeComponentOption> {
 
     private final Map<String, String> overlayPackages = new HashMap<>();
+    private final CustomTheme mOriginalTheme;
 
     public CustomThemeManager(@Nullable CustomTheme existingTheme) {
         if (existingTheme != null && existingTheme.isDefined()) {
+            mOriginalTheme = existingTheme;
             overlayPackages.putAll(existingTheme.getPackagesByCategory());
+        } else {
+            mOriginalTheme = null;
         }
     }
 
@@ -53,12 +57,17 @@
     }
 
     public CustomTheme buildPartialCustomTheme(Context context) {
-        return new CustomTheme(context.getString(R.string.custom_theme_title),
+        return new CustomTheme(mOriginalTheme != null
+                ? mOriginalTheme.getTitle() : context.getString(R.string.custom_theme_title),
                 overlayPackages, null);
     }
 
     @Override
-    public void fetchOptions(OptionsFetchedListener<ThemeComponentOption> callback) {
+    public void fetchOptions(OptionsFetchedListener<ThemeComponentOption> callback, boolean reload) {
         //Unused
     }
+
+    public CustomTheme getOriginalTheme() {
+        return mOriginalTheme;
+    }
 }
diff --git a/src/com/android/customization/picker/clock/ClockFragment.java b/src/com/android/customization/picker/clock/ClockFragment.java
index 5e51dab..714a853 100644
--- a/src/com/android/customization/picker/clock/ClockFragment.java
+++ b/src/com/android/customization/picker/clock/ClockFragment.java
@@ -57,7 +57,7 @@
     }
 
     private RecyclerView mOptionsContainer;
-    private OptionSelectorController mOptionsController;
+    private OptionSelectorController<Clockface> mOptionsController;
     private Clockface mSelectedOption;
     private ClockManager mClockManager;
     private PreviewPager mPreviewPager;
@@ -101,7 +101,7 @@
 
     private void setUpOptions() {
         mClockManager.fetchOptions(options -> {
-            mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+            mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
 
             mOptionsController.addListener(selected -> {
                 mSelectedOption = (Clockface) selected;
@@ -118,7 +118,7 @@
                 mSelectedOption = options.get(0);
             }
             createAdapter();
-        });
+        }, false);
     }
 
     private static class ClockfacePreviewPage extends PreviewPage {
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
index 8d7a5c1..75ff566 100644
--- a/src/com/android/customization/picker/grid/GridFragment.java
+++ b/src/com/android/customization/picker/grid/GridFragment.java
@@ -77,7 +77,7 @@
     private BitmapDrawable mCardBackground;
     private GridPreviewAdapter mAdapter;
     private RecyclerView mOptionsContainer;
-    private OptionSelectorController mOptionsController;
+    private OptionSelectorController<GridOption> mOptionsController;
     private GridOptionsManager mGridManager;
     private GridOption mSelectedOption;
     private PreviewPager mPreviewPager;
@@ -158,7 +158,7 @@
 
     private void setUpOptions() {
         mGridManager.fetchOptions(options -> {
-            mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+            mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
 
             mOptionsController.addListener(selected -> {
                 mSelectedOption = (GridOption) selected;
@@ -175,7 +175,7 @@
                 mSelectedOption = options.get(0);
             }
             createAdapter();
-        });
+        }, false);
     }
 
     private class GridPreviewPage extends PreviewPage {
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 6b39a78..0fd2b6b 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -56,6 +56,9 @@
         CustomThemeComponentFragmentHost {
     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;
+    public static final int RESULT_THEME_DELETED = 10;
+    public static final int RESULT_THEME_APPLIED = 20;
 
     private static final String TAG = "CustomThemeActivity";
 
@@ -103,6 +106,7 @@
             // Navigate to the first step
             navigateToStep(0);
         }
+
     }
 
     private void navigateToStep(int i) {
@@ -145,6 +149,7 @@
                         public void onSuccess() {
                             Toast.makeText(CustomThemeActivity.this, R.string.applied_theme_msg,
                                     Toast.LENGTH_LONG).show();
+                            setResult(RESULT_THEME_APPLIED);
                             finish();
                         }
 
@@ -186,12 +191,14 @@
 
     @Override
     public void delete() {
-
+        mThemeManager.removeCustomTheme(mCustomThemeManager.getOriginalTheme());
+        setResult(RESULT_THEME_DELETED);
+        finish();
     }
 
     @Override
     public void cancel() {
-
+        finish();
     }
 
     @Override
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
index eda44c9..3ee29b0 100644
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
@@ -15,9 +15,11 @@
  */
 package com.android.customization.picker.theme;
 
+import android.app.AlertDialog;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.LayoutInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -70,7 +72,7 @@
     @StringRes private int mTitleResId;
 
     private RecyclerView mOptionsContainer;
-    private OptionSelectorController mOptionsController;
+    private OptionSelectorController<ThemeComponentOption> mOptionsController;
     private CardView mPreviewCard;
     private TextView mTitle;
     private ThemeComponentOption mSelectedOption;
@@ -97,7 +99,14 @@
             @Nullable Bundle savedInstanceState) {
         View view = inflater.inflate(
                 R.layout.fragment_custom_theme_component, container, /* attachToRoot */ false);
-        setUpToolbar(view);
+        // 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.setNavigationIcon(getResources().getDrawable(R.drawable.ic_close_24px, null));
+        mToolbar.setNavigationOnClickListener(v -> mHost.cancel());
         mOptionsContainer = view.findViewById(R.id.options_container);
         mPreviewCard = view.findViewById(R.id.component_preview_card);
         mTitle = view.findViewById(R.id.component_options_title);
@@ -116,6 +125,21 @@
         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);
+    }
+
     public ThemeComponentOption getSelectedOption() {
         return mSelectedOption;
     }
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index e60e17b..2fbdbd8 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -84,7 +84,7 @@
 
     private RecyclerView mOptionsContainer;
     private CheckBox mUseMyWallpaperButton;
-    private OptionSelectorController mOptionsController;
+    private OptionSelectorController<ThemeBundle> mOptionsController;
     private ThemeManager mThemeManager;
     private ThemeBundle mSelectedTheme;
     private ThemePreviewAdapter mAdapter;
@@ -152,6 +152,20 @@
         }
     }
 
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME) {
+            if (resultCode == CustomThemeActivity.RESULT_THEME_DELETED) {
+                mSelectedTheme = null;
+                reloadOptions();
+            }
+            if (resultCode == CustomThemeActivity.RESULT_THEME_APPLIED) {
+                getActivity().finish();
+            }
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
     private void onUseMyWallpaperCheckChanged(CompoundButton checkbox, boolean checked) {
         mUseMyWallpaper = checked;
         reloadWallpaper();
@@ -199,7 +213,7 @@
 
     private void setUpOptions(@Nullable Bundle savedInstanceState) {
         mThemeManager.fetchOptions(options -> {
-            mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+            mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);
             mOptionsController.addListener(selected -> {
                 if (selected instanceof CustomTheme && !((CustomTheme) selected).isDefined()) {
                     navigateToCustomTheme(null);
@@ -228,7 +242,27 @@
                 mSelectedTheme = options.get(0);
             }
             mOptionsController.setSelectedOption(mSelectedTheme);
-        });
+        }, false);
+        createAdapter();
+        updateButtonsVisibility();
+    }
+
+    private void reloadOptions() {
+        mThemeManager.fetchOptions(options -> {
+            mOptionsController.resetOptions(options);
+            for (ThemeBundle theme : options) {
+                if (theme.isActive(mThemeManager)) {
+                    mSelectedTheme = theme;
+                    break;
+                }
+            }
+            if (mSelectedTheme == null) {
+                // Select the default theme if there is no matching custom enabled theme
+                // TODO(b/124796742): default to custom if there is no matching theme bundle
+                mSelectedTheme = options.get(0);
+            }
+            mOptionsController.setSelectedOption(mSelectedTheme);
+        }, true);
         createAdapter();
         updateButtonsVisibility();
     }
@@ -240,7 +274,7 @@
             intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES,
                     themeToEdit.getSerializedPackages());
         }
-        startActivity(intent);
+        startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME);
     }
 
     private static abstract class ThemePreviewPage extends PreviewPage {
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index 10b6d28..090dc7e 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -37,9 +37,10 @@
  * Simple controller for a RecyclerView-based widget to hold the options for each customization
  * section (eg, thumbnails for themes, clocks, grid sizes).
  * To use, just pass the RV that will contain the tiles and the list of {@link CustomizationOption}
- * representing each option, and call {@link #initOptions()} to populate the widget.
+ * representing each option, and call {@link #initOptions(CustomizationManager)} to populate the
+ * widget.
  */
-public class OptionSelectorController {
+public class OptionSelectorController<T extends CustomizationOption<T>> {
 
     /**
      * Interface to be notified when an option is selected by the user.
@@ -52,14 +53,13 @@
     }
 
     private final RecyclerView mContainer;
-    private final List<? extends CustomizationOption> mOptions;
+    private final List<T> mOptions;
 
     private final Set<OptionSelectedListener> mListeners = new HashSet<>();
     private RecyclerView.Adapter<TileViewHolder> mAdapter;
     private CustomizationOption mSelectedOption;
 
-    public OptionSelectorController(RecyclerView container,
-            List<? extends CustomizationOption> options) {
+    public OptionSelectorController(RecyclerView container, List<T> options) {
         mContainer = container;
         mOptions = options;
     }
@@ -84,7 +84,7 @@
     /**
      * Initializes the UI for the options passed in the constructor of this class.
      */
-    public void initOptions(final CustomizationManager<? extends CustomizationOption> manager) {
+    public void initOptions(final CustomizationManager<T> manager) {
         mAdapter = new RecyclerView.Adapter<TileViewHolder>() {
             @Override
             public int getItemViewType(int position) {
@@ -126,6 +126,12 @@
         mContainer.setAdapter(mAdapter);
     }
 
+    public void resetOptions(List<T> options) {
+        mOptions.clear();
+        mOptions.addAll(options);
+        mAdapter.notifyDataSetChanged();
+    }
+
     private void notifyListeners() {
         if (mListeners.isEmpty()) {
             return;