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;