diff --git a/res/drawable/preference_circular_icons_plus_item_background.xml b/res/drawable/preference_circular_icons_plus_item_background.xml
new file mode 100644
index 00000000000..8200a9b12eb
--- /dev/null
+++ b/res/drawable/preference_circular_icons_plus_item_background.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/preference_circular_icons_item.xml b/res/layout/preference_circular_icons_item.xml
index 3e8d7fa1f2b..e5656ce599b 100644
--- a/res/layout/preference_circular_icons_item.xml
+++ b/res/layout/preference_circular_icons_item.xml
@@ -17,8 +17,8 @@
\ No newline at end of file
diff --git a/res/layout/preference_circular_icons_plus_item.xml b/res/layout/preference_circular_icons_plus_item.xml
new file mode 100644
index 00000000000..98820863410
--- /dev/null
+++ b/res/layout/preference_circular_icons_plus_item.xml
@@ -0,0 +1,32 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c76fff5965e..2bb8fc2ba0f 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -509,7 +509,8 @@
56dp
32dp
- 32dp
+ 32dp
+ 20dp
4dp
8dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 42bbe27d4b7..ec2ff5558b3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9366,6 +9366,8 @@
%s (Work)
Calculating\u2026
+
+ +%d
Allow apps to override
diff --git a/src/com/android/settings/notification/modes/CircularIconSet.java b/src/com/android/settings/notification/modes/CircularIconSet.java
index 55a92fd749c..18f82d97de0 100644
--- a/src/com/android/settings/notification/modes/CircularIconSet.java
+++ b/src/com/android/settings/notification/modes/CircularIconSet.java
@@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
import androidx.annotation.VisibleForTesting;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -61,6 +62,15 @@ class CircularIconSet {
mCachedIcons = new ConcurrentHashMap<>();
}
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("items", mItems).toString();
+ }
+
+ boolean hasSameItemsAs(CircularIconSet> other) {
+ return other != null && this.mItems.equals(other.mItems);
+ }
+
int size() {
return mItems.size();
}
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
index 1f6e0b0ef34..5e8f720c83c 100644
--- a/src/com/android/settings/notification/modes/CircularIconsPreference.java
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -21,15 +21,17 @@ import static com.google.common.base.Preconditions.checkState;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -37,11 +39,12 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.Utils;
-import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -50,8 +53,9 @@ public class CircularIconsPreference extends RestrictedPreference {
private Executor mUiExecutor;
@Nullable private LinearLayout mIconContainer;
- @Nullable private CircularIconSet> mPendingIconSet;
- @Nullable private ListenableFuture> mPendingLoadIconsFuture;
+ @Nullable private CircularIconSet> mIconSet;
+ @Nullable private CircularIconSet> mPendingDisplayIconSet;
+ @Nullable private ListenableFuture> mPendingLoadIconsFuture;
public CircularIconsPreference(Context context) {
super(context);
@@ -94,17 +98,25 @@ public class CircularIconsPreference extends RestrictedPreference {
}
private void displayIconsIfPending() {
- CircularIconSet> pendingIconSet = mPendingIconSet;
+ CircularIconSet> pendingIconSet = mPendingDisplayIconSet;
if (pendingIconSet != null) {
- mPendingIconSet = null;
- displayIcons(pendingIconSet);
+ mPendingDisplayIconSet = null;
+ displayIconsInternal(pendingIconSet);
}
}
void displayIcons(CircularIconSet> iconSet) {
+ if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet)) {
+ return;
+ }
+ mIconSet = iconSet;
+ displayIconsInternal(iconSet);
+ }
+
+ void displayIconsInternal(CircularIconSet> iconSet) {
if (mIconContainer == null) {
// Too soon, wait for bind.
- mPendingIconSet = iconSet;
+ mPendingDisplayIconSet = iconSet;
return;
}
mIconContainer.setVisibility(iconSet.size() != 0 ? View.VISIBLE : View.GONE);
@@ -113,30 +125,31 @@ public class CircularIconsPreference extends RestrictedPreference {
}
if (mIconContainer.getMeasuredWidth() == 0) {
// Too soon, wait for first measure to know width.
- mPendingIconSet = iconSet;
- ViewTreeObserver vto = mIconContainer.getViewTreeObserver();
- vto.addOnGlobalLayoutListener(() ->
+ mPendingDisplayIconSet = iconSet;
+ mIconContainer.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- vto.removeOnGlobalLayoutListener(this);
+ checkNotNull(mIconContainer).getViewTreeObserver()
+ .removeOnGlobalLayoutListener(this);
displayIconsIfPending();
}
- });
+ }
+ );
return;
}
mIconContainer.setVisibility(View.VISIBLE);
Resources res = getContext().getResources();
int availableSpace = mIconContainer.getMeasuredWidth();
- int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
+ int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
int numIconsThatFit = availableSpace / iconHorizontalSpace;
List> iconFutures;
- int extraItems = 0;
+ int extraItems;
if (iconSet.size() > numIconsThatFit) {
- // Reserve one space for the (+xx) circle.
+ // Reserve one space for the (+xx) textview.
int numIconsToShow = numIconsThatFit - 1;
if (numIconsToShow < 0) {
numIconsToShow = 0;
@@ -146,6 +159,7 @@ public class CircularIconsPreference extends RestrictedPreference {
} else {
// Fit exactly or with remaining space.
iconFutures = iconSet.getIcons();
+ extraItems = 0;
}
displayIconsWhenReady(iconFutures, extraItems);
@@ -158,33 +172,45 @@ public class CircularIconsPreference extends RestrictedPreference {
mPendingLoadIconsFuture.cancel(true);
}
- int numCircles = iconFutures.size() + (extraItems > 0 ? 1 : 0);
- if (mIconContainer.getChildCount() > numCircles) {
- mIconContainer.removeViews(numCircles, mIconContainer.getChildCount() - numCircles);
+ // Rearrange child views until we have ImageViews...
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ int numImages = iconFutures.size();
+ int numImageViews = getChildCount(mIconContainer, ImageView.class);
+ if (numImages > numImageViews) {
+ for (int i = 0; i < numImages - numImageViews; i++) {
+ ImageView imageView = (ImageView) inflater.inflate(
+ R.layout.preference_circular_icons_item, mIconContainer, false);
+ mIconContainer.addView(imageView, 0);
+ }
+ } else if (numImageViews > numImages) {
+ for (int i = 0; i < numImageViews - numImages; i++) {
+ mIconContainer.removeViewAt(0);
+ }
}
- for (int i = mIconContainer.getChildCount(); i < numCircles; i++) {
- ImageView imageView = (ImageView) LayoutInflater.from(getContext()).inflate(
- R.layout.preference_circular_icons_item, mIconContainer, false);
- mIconContainer.addView(imageView);
+ // ... plus 0/1 TextViews at the end.
+ if (extraItems > 0 && !(getLastChild(mIconContainer) instanceof TextView)) {
+ // TODO: b/346551087 - Check TODO in preference_circular_icons_plus_item_background
+ TextView plusView = (TextView) inflater.inflate(
+ R.layout.preference_circular_icons_plus_item, mIconContainer, false);
+ mIconContainer.addView(plusView);
+ } else if (extraItems == 0 && (getLastChild(mIconContainer) instanceof TextView)) {
+ mIconContainer.removeViewAt(mIconContainer.getChildCount() - 1);
}
// Set up placeholders and extra items indicator.
- for (int i = 0; i < iconFutures.size(); i++) {
+ for (int i = 0; i < numImages; i++) {
ImageView imageView = (ImageView) mIconContainer.getChildAt(i);
- // TODO: b/346551087 - proper color and shape, should be a gray circle.
- imageView.setImageDrawable(new ColorDrawable(Color.RED));
+ imageView.setImageDrawable(getPlaceholderImage(getContext()));
}
if (extraItems > 0) {
- ImageView imageView = (ImageView) mIconContainer.getChildAt(
- mIconContainer.getChildCount() - 1);
- // TODO: b/346551087 - proper color and shape and number.
- imageView.setImageDrawable(new ColorDrawable(Color.BLUE));
+ TextView textView = (TextView) checkNotNull(getLastChild(mIconContainer));
+ textView.setText(getContext().getString(R.string.zen_mode_plus_n_items, extraItems));
}
// Display icons when all are ready (more consistent than randomly loading).
mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
FutureUtil.whenDone(
- Futures.allAsList(iconFutures),
+ mPendingLoadIconsFuture,
icons -> {
checkState(mIconContainer != null);
for (int i = 0; i < icons.size(); i++) {
@@ -194,15 +220,54 @@ public class CircularIconsPreference extends RestrictedPreference {
mUiExecutor);
}
+ private static Drawable getPlaceholderImage(Context context) {
+ ShapeDrawable placeholder = new ShapeDrawable(new OvalShape());
+ placeholder.setTintList(Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorSecondaryContainer));
+ return placeholder;
+ }
+
+ private static int getChildCount(ViewGroup parent, Class extends View> childClass) {
+ int count = 0;
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ if (childClass.isInstance(parent.getChildAt(i))) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Nullable
+ private static View getLastChild(ViewGroup parent) {
+ if (parent.getChildCount() == 0) {
+ return null;
+ }
+ return parent.getChildAt(parent.getChildCount() - 1);
+ }
+
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
- ImmutableList getIconViews() {
+ List getIcons() {
if (mIconContainer == null) {
- return ImmutableList.of();
+ return List.of();
}
- ImmutableList.Builder imageViews = new ImmutableList.Builder<>();
- for (int i = 0; i < mIconContainer.getChildCount(); i++) {
- imageViews.add((ImageView) mIconContainer.getChildAt(i));
+ ArrayList drawables = new ArrayList<>();
+ for (int i = 0; i < getChildCount(mIconContainer, ImageView.class); i++) {
+ drawables.add(((ImageView) mIconContainer.getChildAt(i)).getDrawable());
+ }
+ return drawables;
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ @Nullable
+ String getPlusText() {
+ if (mIconContainer == null) {
+ return null;
+ }
+ View lastChild = getLastChild(mIconContainer);
+ if (lastChild instanceof TextView tv) {
+ return tv.getText() != null ? tv.getText().toString() : null;
+ } else {
+ return null;
}
- return imageViews.build();
}
}
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index d07abf34b79..07e14407353 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -55,7 +55,7 @@ class IconUtil {
* Returns a variant of the supplied {@code icon} to be used as the header in the icon picker.
* The inner icon is 48x48dp and it's contained into a circle of diameter 90dp.
*/
- static Drawable makeBigIconCircle(@NonNull Context context, Drawable icon) {
+ static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
return composeIconCircle(
Utils.getColorAttr(context,
com.android.internal.R.attr.materialColorSecondaryContainer),
@@ -73,7 +73,7 @@ class IconUtil {
* The inner icon 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 makeSmallIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
+ static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
return composeIconCircle(
context.getColorStateList(R.color.modes_icon_picker_item_background),
context.getResources().getDimensionPixelSize(
@@ -84,6 +84,24 @@ class IconUtil {
R.dimen.zen_mode_icon_list_item_icon_size));
}
+ /**
+ * Returns a variant of the supplied icon to be used in a {@link CircularIconsPreference}. The
+ * inner icon is 20x20 dp and it's contained in a circle of diameter 32dp, and is tinted
+ * with the "material secondary container" color combination.
+ */
+ static Drawable makeSoundIcon(@NonNull Context context, @DrawableRes int iconResId) {
+ return composeIconCircle(
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorSecondaryContainer),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_circular_icon_diameter),
+ checkNotNull(context.getDrawable(iconResId)),
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorOnSecondaryContainer),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_circular_icon_inner_icon_size));
+ }
+
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
@@ -93,11 +111,11 @@ class IconUtil {
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
- layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
layerDrawable.setLayerSize(0, circleDiameterPx, circleDiameterPx);
layerDrawable.setLayerGravity(1, Gravity.CENTER);
layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
+ layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
return layerDrawable;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
index 70df9b651ee..1b51cfa6c35 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -64,7 +64,7 @@ class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenc
FutureUtil.whenDone(
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
- icon -> mHeaderController.setIcon(IconUtil.makeBigIconCircle(mContext, icon))
+ icon -> mHeaderController.setIcon(IconUtil.makeIconPickerHeader(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
index 512dabb4437..93df38b61a5 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -156,7 +156,7 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
- info -> IconUtil.makeSmallIconCircle(mContext, info.resId()));
+ info -> IconUtil.makeIconPickerItem(mContext, info.resId()));
holder.bindIcon(iconInfo, iconDrawable);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 452faed3f8d..fce48afe443 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -17,19 +17,44 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
+import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
/**
* Preference with a link and summary about what other sounds can break through the mode
*/
class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
+ // TODO: b/346551087 - Use proper icons
+ private static final ImmutableMap* @PriorityCategory */ Integer, /* @DrawableRes */ Integer>
+ PRIORITIES_TO_ICONS = ImmutableMap.of(
+ PRIORITY_CATEGORY_ALARMS,
+ com.android.internal.R.drawable.ic_audio_alarm,
+ PRIORITY_CATEGORY_MEDIA,
+ com.android.settings.R.drawable.ic_media_stream,
+ PRIORITY_CATEGORY_SYSTEM,
+ com.android.settings.R.drawable.ic_settings_keyboards,
+ PRIORITY_CATEGORY_REMINDERS,
+ com.android.internal.R.drawable.ic_popup_reminder,
+ PRIORITY_CATEGORY_EVENTS,
+ com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
@@ -51,7 +76,17 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
zenMode.getId(), 0).toIntent());
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
- // TODO: b/346551087 - Show media icons
- ((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
+ ((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy()));
+ }
+
+ private CircularIconSet getSoundIcons(ZenPolicy policy) {
+ ImmutableList.Builder icons = new ImmutableList.Builder<>();
+ for (Map.Entry entry : PRIORITIES_TO_ICONS.entrySet()) {
+ if (policy.isCategoryAllowed(entry.getKey(), false)) {
+ icons.add(entry.getValue());
+ }
+ }
+ return new CircularIconSet<>(icons.build(),
+ iconResId -> IconUtil.makeSoundIcon(mContext, iconResId));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index 26de9eedf33..dd3a400e886 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -56,6 +56,8 @@ import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.notification.modes.ZenMode;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -85,14 +87,18 @@ class ZenModeSummaryHelper {
PRIORITY_CATEGORY_REPEAT_CALLERS,
};
+ static final ImmutableList* @PriorityCategory */ Integer> OTHER_SOUND_CATEGORIES =
+ ImmutableList.of(
+ PRIORITY_CATEGORY_ALARMS,
+ PRIORITY_CATEGORY_MEDIA,
+ PRIORITY_CATEGORY_SYSTEM,
+ PRIORITY_CATEGORY_REMINDERS,
+ PRIORITY_CATEGORY_EVENTS);
+
String getOtherSoundCategoriesSummary(ZenMode zenMode) {
List enabledCategories = getEnabledCategories(
zenMode.getPolicy(),
- category -> PRIORITY_CATEGORY_ALARMS == category
- || PRIORITY_CATEGORY_MEDIA == category
- || PRIORITY_CATEGORY_SYSTEM == category
- || PRIORITY_CATEGORY_REMINDERS == category
- || PRIORITY_CATEGORY_EVENTS == category,
+ OTHER_SOUND_CATEGORIES::contains,
true);
int numCategories = enabledCategories.size();
MessageFormat msgFormat = new MessageFormat(
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
index 22dc7547959..9e8524395e5 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
@@ -53,6 +53,36 @@ public class CircularIconSetTest {
when(mDrawableLoader.apply(anyInt())).thenReturn(new ColorDrawable(Color.BLACK));
}
+ @Test
+ public void equals_sameItems_true() {
+ CircularIconSet items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
+ num -> new ColorDrawable(Color.BLUE));
+ CircularIconSet items2 = new CircularIconSet<>(ImmutableList.of(1, 2),
+ num -> new ColorDrawable(Color.GREEN));
+
+ assertThat(items1.hasSameItemsAs(items2)).isTrue();
+ }
+
+ @Test
+ public void equals_differentTypes_false() {
+ CircularIconSet items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
+ num -> new ColorDrawable(Color.BLUE));
+ CircularIconSet items2 = new CircularIconSet<>(ImmutableList.of("a", "b"),
+ str -> new ColorDrawable(Color.GREEN));
+
+ assertThat(items1.hasSameItemsAs(items2)).isFalse();
+ }
+
+ @Test
+ public void equals_differentItems_false() {
+ CircularIconSet items1 = new CircularIconSet<>(ImmutableList.of("a", "b"),
+ str -> new ColorDrawable(Color.GREEN));
+ CircularIconSet items2 = new CircularIconSet<>(ImmutableList.of("a", "b", "c"),
+ str -> new ColorDrawable(Color.GREEN));
+
+ assertThat(items1.hasSameItemsAs(items2)).isFalse();
+ }
+
@Test
public void getIcons_loadsAllIcons() {
CircularIconSet set = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
index 2ef62d0d29c..73754df349b 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
@@ -19,16 +19,16 @@ package com.android.settings.notification.modes;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.ImageView;
import androidx.preference.PreferenceViewHolder;
@@ -41,11 +41,11 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import java.util.List;
import java.util.stream.IntStream;
@RunWith(RobolectricTestRunner.class)
@@ -68,20 +68,30 @@ public class CircularIconsPreferenceTest {
// Tests should call bindAndMeasureViewHolder() so that icons can be added.
Resources res = mContext.getResources();
- mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
+ mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
}
private void bindAndMeasureViewHolder(int viewWidth) {
+ bindViewHolder();
+ measureViewHolder(viewWidth);
+ }
+
+ private void bindViewHolder() {
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
null);
mIconContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
- mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
- makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
mPreference.onBindViewHolder(holder);
}
+ private void measureViewHolder(int viewWidth) {
+ checkState(mIconContainer != null, "Call bindViewHolder() first!");
+ mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
+ makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+ mIconContainer.getViewTreeObserver().dispatchOnGlobalLayout();
+ }
+
@Test
public void displayIcons_loadsIcons() {
CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
@@ -90,13 +100,10 @@ public class CircularIconsPreferenceTest {
bindAndMeasureViewHolder(VIEW_WIDTH);
mPreference.displayIcons(iconSet);
- assertThat(mPreference.getIconViews()).hasSize(2);
- assertThat(mPreference.getIconViews().get(0).getDrawable())
- .isInstanceOf(ColorDrawable.class);
- assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
- .isEqualTo(1);
- assertThat(((ColorDrawable) mPreference.getIconViews().get(1).getDrawable()).getColor())
- .isEqualTo(2);
+ assertThat(mPreference.getIcons()).hasSize(2);
+ assertThat(((ColorDrawable) mPreference.getIcons().get(0)).getColor()).isEqualTo(1);
+ assertThat(((ColorDrawable) mPreference.getIcons().get(1)).getColor()).isEqualTo(2);
+ assertThat(mPreference.getPlusText()).isNull();
assertThat(mIconContainer.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -111,74 +118,81 @@ public class CircularIconsPreferenceTest {
assertThat(mIconContainer.getVisibility()).isEqualTo(View.GONE);
}
-
@Test
public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
int width = 300;
- int fittingIcons = width / mOneIconWidth;
+ int fittingCircles = width / mOneIconWidth;
CircularIconSet iconSet = new CircularIconSet<>(
- IntStream.range(0, fittingIcons).boxed().toList(),
+ IntStream.range(0, fittingCircles).boxed().toList(),
ColorDrawable::new);
bindAndMeasureViewHolder(width);
mPreference.displayIcons(iconSet);
- List displayedDrawables = mPreference.getIconViews().stream()
- .map(ImageView::getDrawable).toList();
- assertThat(displayedDrawables).hasSize(fittingIcons);
- assertThat(displayedDrawables).containsExactlyElementsIn(
+ assertThat(mPreference.getIcons()).hasSize(fittingCircles);
+ assertThat(mPreference.getIcons()).containsExactlyElementsIn(
Futures.allAsList(iconSet.getIcons()).get()).inOrder();
+ assertThat(mPreference.getPlusText()).isNull();
+
}
@Test
public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
int width = 300;
- int fittingIcons = width / mOneIconWidth;
+ int fittingCircles = width / mOneIconWidth;
CircularIconSet iconSet = new CircularIconSet<>(
- IntStream.range(0, fittingIcons + 5).boxed().toList(),
+ IntStream.range(0, fittingCircles + 5).boxed().toList(),
ColorDrawable::new);
bindAndMeasureViewHolder(width);
mPreference.displayIcons(iconSet);
- List displayedDrawables = mPreference.getIconViews().stream()
- .map(ImageView::getDrawable).toList();
- assertThat(displayedDrawables).hasSize(fittingIcons);
- // N-1 are actual icons, Nth icon is (+xx).
- assertThat(displayedDrawables.stream().limit(fittingIcons - 1).toList())
- .containsExactlyElementsIn(
- Futures.allAsList(iconSet.getIcons(fittingIcons - 1)).get())
+ // N-1 icons, plus (+6) text.
+ assertThat(mPreference.getIcons()).hasSize(fittingCircles - 1);
+ assertThat(mPreference.getIcons()).containsExactlyElementsIn(
+ Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get())
.inOrder();
- // TODO: b/346551087 - Correctly verify the plus-6 icon, once we generate it properly.
- assertThat(((ColorDrawable) displayedDrawables.get(
- displayedDrawables.size() - 1)).getColor()).isEqualTo(Color.BLUE);
+ assertThat(mPreference.getPlusText()).isEqualTo("+6");
}
@Test
public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
- int width = 1;
CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
ColorDrawable::new);
- bindAndMeasureViewHolder(width);
+ bindAndMeasureViewHolder(1);
mPreference.displayIcons(iconSet);
- assertThat(mPreference.getIconViews()).hasSize(1);
- // TODO: b/346551087 - Correctly verify the plus-2 icon, once we generate it properly.
- assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
- .isEqualTo(Color.BLUE);
+ assertThat(mPreference.getIcons()).isEmpty();
+ assertThat(mPreference.getPlusText()).isEqualTo("+2");
}
@Test
- public void displayIcons_beforeBind_loadsIconsOnBind() {
+ public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() {
CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
ColorDrawable::new);
mPreference.displayIcons(iconSet);
- assertThat(mPreference.getIconViews()).isEmpty();
+ assertThat(mPreference.getIcons()).isEmpty(); // Hold...
- bindAndMeasureViewHolder(VIEW_WIDTH);
- assertThat(mPreference.getIconViews()).hasSize(3);
+ bindViewHolder();
+ assertThat(mPreference.getIcons()).isEmpty(); // Hooooold...
+
+ measureViewHolder(VIEW_WIDTH);
+ assertThat(mPreference.getIcons()).hasSize(3);
+ }
+
+ @Test
+ public void displayIcons_beforeMeasure_loadsIconsOnMeasure() {
+ CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ ColorDrawable::new);
+ bindViewHolder();
+
+ mPreference.displayIcons(iconSet);
+ assertThat(mPreference.getIcons()).isEmpty();
+
+ measureViewHolder(VIEW_WIDTH);
+ assertThat(mPreference.getIcons()).hasSize(3);
}
@Test
@@ -192,10 +206,24 @@ public class CircularIconsPreferenceTest {
bindAndMeasureViewHolder(VIEW_WIDTH);
mPreference.displayIcons(threeIcons);
- assertThat(mPreference.getIconViews()).hasSize(3);
+ assertThat(mPreference.getIcons()).hasSize(3);
mPreference.displayIcons(twoIcons);
- assertThat(mPreference.getIconViews()).hasSize(2);
+ assertThat(mPreference.getIcons()).hasSize(2);
mPreference.displayIcons(fourIcons);
- assertThat(mPreference.getIconViews()).hasSize(4);
+ assertThat(mPreference.getIcons()).hasSize(4);
+ }
+
+ @Test
+ public void displayIcons_sameSet_doesNotReloadIcons() {
+ CircularIconSet one = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ ColorDrawable::new);
+ CircularIconSet same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ ColorDrawable::new));
+ when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!"));
+
+ bindAndMeasureViewHolder(VIEW_WIDTH);
+
+ mPreference.displayIcons(one);
+ mPreference.displayIcons(same); // if no exception, wasn't called.
}
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 4a6c59627bd..cc4d30643f7 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -259,7 +259,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
appEntries.add(createAppEntry("test2", mContext.getUserId()));
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
- assertThat(mPreference.getIconViews()).hasSize(2);
+ assertThat(mPreference.getIcons()).hasSize(2);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index c9ea6d4ac69..7fa4f9f0e71 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -17,7 +17,7 @@
package com.android.settings.notification.modes;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -25,8 +25,10 @@ import android.app.Flags;
import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenPolicy;
import com.android.settingslib.notification.modes.TestModeBuilder;
+import com.android.settingslib.notification.modes.ZenMode;
import org.junit.Before;
import org.junit.Rule;
@@ -60,13 +62,40 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
}
@Test
- @EnableFlags(Flags.FLAG_MODES_UI)
- public void testHasSummary() {
+ public void updateState_loadsSummary() {
CircularIconsPreference pref = mock(CircularIconsPreference.class);
-
mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
- verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
+ }
+
+ @Test
+ public void updateState_loadsIcons() {
+ CircularIconsPreference pref = mock(CircularIconsPreference.class);
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowMedia(true)
+ .allowSystem(true)
+ .allowReminders(true)
+ .build())
+ .build();
+
+ mController.updateState(pref, mode);
+
+ verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3));
+ }
+
+ @Test
+ public void updateState_loadsAllIcons() {
+ CircularIconsPreference pref = mock(CircularIconsPreference.class);
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
+ .build();
+
+ mController.updateState(pref, mode);
+
+ verify(pref).displayIcons(argThat(iconSet ->
+ iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size()));
}
}
\ No newline at end of file