diff --git a/res/color/modes_icon_picker_item_background.xml b/res/color/modes_icon_picker_item_background.xml
new file mode 100644
index 00000000000..f9280c60d6c
--- /dev/null
+++ b/res/color/modes_icon_picker_item_background.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/color/modes_icon_picker_item_icon.xml b/res/color/modes_icon_picker_item_icon.xml
new file mode 100644
index 00000000000..8a517d5f474
--- /dev/null
+++ b/res/color/modes_icon_picker_item_icon.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index 1e653bf03fe..56967c89d00 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -50,14 +50,16 @@ class IconUtil {
/**
* Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon
- * is 36x36dp and it's contained into a circle of diameter 54dp.
+ * is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up so that
+ * selection and pressed states are represented in the color.
*/
static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
- background.getPaint().setColor(Utils.getColorAttrDefaultColor(context,
- com.android.internal.R.attr.materialColorSecondaryContainer));
- icon.setTint(Utils.getColorAttrDefaultColor(context,
- com.android.internal.R.attr.materialColorOnSecondaryContainer));
+ background.setTintList(
+ context.getColorStateList(R.color.modes_icon_picker_item_background));
+ icon = icon.mutate();
+ icon.setTintList(
+ context.getColorStateList(R.color.modes_icon_picker_item_icon));
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 5897c4dd680..63ed8395006 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -18,13 +18,14 @@ package com.android.settings.notification.modes;
import android.app.AlertDialog;
import android.app.Application;
-import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.annotation.NonNull;
+
import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -72,11 +73,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// Set title for the entire screen
ZenMode mode = getMode();
- AutomaticZenRule azr = getAZR();
- if (mode == null || azr == null) {
- return;
+ if (mode != null) {
+ requireActivity().setTitle(mode.getName());
}
- getActivity().setTitle(azr.getName());
}
@Override
@@ -92,7 +91,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
}
@Override
- protected boolean onOptionsItemSelected(MenuItem item, ZenMode zenMode) {
+ protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
switch (item.getItemId()) {
case DELETE_MODE:
new AlertDialog.Builder(mContext)
diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
index d08f7ea0229..b0ad7956a84 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
@@ -18,7 +18,6 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
-import android.app.AutomaticZenRule;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
@@ -34,7 +33,10 @@ import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
+import com.google.common.base.Preconditions;
+
import java.util.List;
+import java.util.function.Consumer;
/**
* Base class for Settings pages used to configure individual modes.
@@ -175,14 +177,15 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
return mZenMode;
}
- /**
- * Get AutomaticZenRule associated with current mode data, or null if it doesn't exist.
- */
- @Nullable
- public AutomaticZenRule getAZR() {
- if (mZenMode == null) {
- return null;
+ protected final boolean saveMode(Consumer updater) {
+ Preconditions.checkState(mBackend != null);
+ ZenMode mode = mZenMode;
+ if (mode == null) {
+ Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
+ return false;
}
- return mZenMode.getRule();
+ updater.accept(mode);
+ mBackend.updateMode(mode);
+ return true;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
index 760b1835f70..43d9dba1b54 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
@@ -43,7 +43,16 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
return ImmutableList.of(
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
mBackend),
- new ZenModeIconPickerListPreferenceController(context, "icon_list", this,
- new IconOptionsProviderImpl(mContext), mBackend));
+ new ZenModeIconPickerListPreferenceController(context, "icon_list",
+ mIconPickerListener, new IconOptionsProviderImpl(mContext), mBackend));
}
+
+ private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =
+ new ZenModeIconPickerListPreferenceController.IconPickerListener() {
+ @Override
+ public void onIconSelected(int iconResId) {
+ saveMode(mode -> mode.getRule().setIconResId(iconResId));
+ finish();
+ }
+ };
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
index 85ceafe0870..e663354231e 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -25,31 +26,35 @@ import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.settings.R;
-import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.HashMap;
+import java.util.Map;
class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
- private final DashboardFragment mFragment;
private final IconOptionsProvider mIconOptionsProvider;
+ private final IconPickerListener mListener;
@Nullable private IconAdapter mAdapter;
+ private @DrawableRes int mCurrentIconResId;
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
- @NonNull DashboardFragment fragment, @NonNull IconOptionsProvider iconOptionsProvider,
+ @NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
@Nullable ZenModesBackend backend) {
super(context, key, backend);
- mFragment = fragment;
+ mListener = listener;
mIconOptionsProvider = iconOptionsProvider;
}
@@ -68,20 +73,34 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
recyclerView.setAdapter(mAdapter);
recyclerView.setHasFixedSize(true);
- }
-
- @VisibleForTesting
- void onIconSelected(@DrawableRes int resId) {
- saveMode(mode -> {
- mode.getRule().setIconResId(resId);
- return mode;
- });
- mFragment.finish();
+ if (recyclerView.getItemAnimator() instanceof SimpleItemAnimator animator) {
+ animator.setSupportsChangeAnimations(true);
+ }
}
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
- // Nothing to do, the current icon is shown in a different preference.
+ updateIconSelection(zenMode.getRule().getIconResId());
+ }
+
+ private void updateIconSelection(@DrawableRes int iconResId) {
+ if (iconResId != mCurrentIconResId) {
+ int oldIconResId = mCurrentIconResId;
+ mCurrentIconResId = iconResId;
+ if (mAdapter != null) {
+ mAdapter.notifyIconChanged(oldIconResId);
+ mAdapter.notifyIconChanged(mCurrentIconResId);
+ }
+ }
+ }
+
+ private void onIconSelected(@DrawableRes int iconResId) {
+ updateIconSelection(iconResId);
+ mListener.onIconSelected(iconResId);
+ }
+
+ interface IconPickerListener {
+ void onIconSelected(@DrawableRes int iconResId);
}
private class IconHolder extends RecyclerView.ViewHolder {
@@ -93,20 +112,25 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
mImageView = itemView.findViewById(R.id.icon_image_view);
}
- void bindIcon(IconOptionsProvider.IconInfo icon) {
- mImageView.setImageDrawable(
- IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
+ void bindIcon(IconOptionsProvider.IconInfo icon, Drawable iconDrawable) {
+ mImageView.setImageDrawable(iconDrawable);
itemView.setContentDescription(icon.description());
- itemView.setOnClickListener(v -> onIconSelected(icon.resId()));
+ itemView.setOnClickListener(v -> {
+ itemView.setSelected(true); // Immediately, to avoid flicker until we rebind.
+ onIconSelected(icon.resId());
+ });
+ itemView.setSelected(icon.resId() == mCurrentIconResId);
}
}
private class IconAdapter extends RecyclerView.Adapter {
private final ImmutableList mIconResources;
+ private final Map mIconCache;
private IconAdapter(IconOptionsProvider iconOptionsProvider) {
mIconResources = iconOptionsProvider.getIcons();
+ mIconCache = new HashMap<>();
}
@NonNull
@@ -119,13 +143,24 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
@Override
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
- holder.bindIcon(mIconResources.get(position));
+ IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
+ Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
+ info -> IconUtil.makeIconCircle(mContext, info.resId()));
+ holder.bindIcon(iconInfo, iconDrawable);
}
@Override
public int getItemCount() {
return mIconResources.size();
}
+
+ private void notifyIconChanged(@DrawableRes int iconResId) {
+ int position = Iterables.indexOf(mIconResources,
+ iconInfo -> iconInfo.resId() == iconResId);
+ if (position != -1) {
+ notifyItemChanged(position);
+ }
+ }
}
private static class AutoFitGridLayoutManager extends GridLayoutManager {
diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
index fa12b30590c..26c7fe170c0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
@@ -24,6 +24,7 @@ import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.android.settingslib.notification.modes.ZenMode;
@@ -70,13 +71,13 @@ class TestModeBuilder {
return this;
}
- public TestModeBuilder setName(String name) {
+ TestModeBuilder setName(String name) {
mRule.setName(name);
mConfigZenRule.name = name;
return this;
}
- public TestModeBuilder setPackage(String pkg) {
+ TestModeBuilder setPackage(String pkg) {
mRule.setPackageName(pkg);
mConfigZenRule.pkg = pkg;
return this;
@@ -114,7 +115,7 @@ class TestModeBuilder {
return this;
}
- public TestModeBuilder setEnabled(boolean enabled) {
+ TestModeBuilder setEnabled(boolean enabled) {
mRule.setEnabled(enabled);
mConfigZenRule.enabled = enabled;
return this;
@@ -126,12 +127,17 @@ class TestModeBuilder {
return this;
}
- public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
mRule.setTriggerDescription(triggerDescription);
mConfigZenRule.triggerDescription = triggerDescription;
return this;
}
+ TestModeBuilder setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ return this;
+ }
+
TestModeBuilder setActive(boolean active) {
if (active) {
mConfigZenRule.enabled = true;
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
index 5db7e925eef..e0ca306c71c 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
@@ -24,13 +24,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
-import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
@@ -40,35 +42,34 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class ZenModeIconPickerListPreferenceControllerTest {
- private static final ZenMode ZEN_MODE = TestModeBuilder.EXAMPLE;
-
- private ZenModesBackend mBackend;
+ private Context mContext;
private ZenModeIconPickerListPreferenceController mController;
- private PreferenceScreen mPreferenceScreen;
+ @Mock private PreferenceScreen mPreferenceScreen;
+ private LayoutPreference mLayoutPreference;
private RecyclerView mRecyclerView;
+ @Mock private ZenModeIconPickerListPreferenceController.IconPickerListener mListener;
@Before
public void setUp() {
- Context context = RuntimeEnvironment.getApplication();
- mBackend = mock(ZenModesBackend.class);
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.getApplication();
- DashboardFragment fragment = mock(DashboardFragment.class);
mController = new ZenModeIconPickerListPreferenceController(
- RuntimeEnvironment.getApplication(), "icon_list", fragment,
- new TestIconOptionsProvider(), mBackend);
+ RuntimeEnvironment.getApplication(), "icon_list", mListener,
+ new TestIconOptionsProvider(), mock(ZenModesBackend.class));
- mRecyclerView = new RecyclerView(context);
+ mRecyclerView = new RecyclerView(mContext);
mRecyclerView.setId(R.id.icon_list);
- LayoutPreference layoutPreference = new LayoutPreference(context, mRecyclerView);
- mPreferenceScreen = mock(PreferenceScreen.class);
- when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(layoutPreference);
+ mLayoutPreference = new LayoutPreference(mContext, mRecyclerView);
+ when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(mLayoutPreference);
}
@Test
@@ -80,14 +81,32 @@ public class ZenModeIconPickerListPreferenceControllerTest {
}
@Test
- public void selectIcon_updatesMode() {
- mController.setZenMode(ZEN_MODE);
+ public void updateState_highlightsCurrentIcon() {
+ ZenMode mode = new TestModeBuilder().setIconResId(R.drawable.ic_hearing).build();
+ mController.displayPreference(mPreferenceScreen);
- mController.onIconSelected(R.drawable.ic_android);
+ mController.updateZenMode(mLayoutPreference, mode);
- ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class);
- verify(mBackend).updateMode(captor.capture());
- assertThat(captor.getValue().getRule().getIconResId()).isEqualTo(R.drawable.ic_android);
+ assertThat(getItemViewAt(0).isSelected()).isFalse();
+ assertThat(getItemViewAt(1).isSelected()).isFalse();
+ assertThat(getItemViewAt(2).isSelected()).isTrue();
+ }
+
+ @Test
+ public void performClick_onIconItem_notifiesListener() {
+ mController.displayPreference(mPreferenceScreen);
+
+ getItemViewAt(1).performClick();
+
+ verify(mListener).onIconSelected(R.drawable.ic_info);
+ }
+
+ private View getItemViewAt(int position) {
+ ViewGroup fakeParent = new FrameLayout(mContext);
+ RecyclerView.ViewHolder viewHolder = mRecyclerView.getAdapter().onCreateViewHolder(
+ fakeParent, 0);
+ mRecyclerView.getAdapter().bindViewHolder(viewHolder, position);
+ return viewHolder.itemView;
}
private static class TestIconOptionsProvider implements IconOptionsProvider {