diff --git a/res/drawable/ic_zen_mode_generic_contact.xml b/res/drawable/ic_zen_mode_generic_contact.xml
new file mode 100644
index 00000000000..3721dc5ffd4
--- /dev/null
+++ b/res/drawable/ic_zen_mode_generic_contact.xml
@@ -0,0 +1,25 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_people_all.xml b/res/drawable/ic_zen_mode_people_all.xml
new file mode 100644
index 00000000000..c6194d52911
--- /dev/null
+++ b/res/drawable/ic_zen_mode_people_all.xml
@@ -0,0 +1,25 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2bb8fc2ba0f..68eb99b18da 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -513,4 +513,5 @@
20dp
4dp
8dp
+ 18dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d5259a6a450..f994fbc911d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9424,6 +9424,8 @@
Some people can interrupt
+ Repeat callers can interrupt
+
All people can interrupt
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index 07e14407353..e19da406d69 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -20,6 +20,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
@@ -29,11 +36,16 @@ import android.view.Gravity;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.Px;
import com.android.settings.R;
import com.android.settingslib.Utils;
+import com.google.common.base.Strings;
+
+import java.util.Locale;
+
class IconUtil {
static Drawable applyNormalTint(@NonNull Context context, @NonNull Drawable icon) {
@@ -87,9 +99,10 @@ class IconUtil {
/**
* 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.
+ * with the "material secondary" color combination.
*/
- static Drawable makeSoundIcon(@NonNull Context context, @DrawableRes int iconResId) {
+ static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
+ @DrawableRes int iconResId) {
return composeIconCircle(
Utils.getColorAttr(context,
com.android.internal.R.attr.materialColorSecondaryContainer),
@@ -102,6 +115,53 @@ class IconUtil {
R.dimen.zen_mode_circular_icon_inner_icon_size));
}
+ /**
+ * Returns an icon representing a contact that doesn't have an associated photo, to be used in
+ * a {@link CircularIconsPreference}, tinted with the "material tertiary". If the contact's
+ * display name is not empty, it's the contact's monogram, otherwise it's a generic icon.
+ */
+ static Drawable makeContactMonogram(@NonNull Context context, @Nullable String displayName) {
+ Resources res = context.getResources();
+ if (Strings.isNullOrEmpty(displayName)) {
+ return composeIconCircle(
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorTertiaryContainer),
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter),
+ checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_generic_contact)),
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorOnTertiaryContainer),
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_inner_icon_size));
+ }
+
+ float diameter = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter);
+ Bitmap bitmap = Bitmap.createBitmap((int) diameter, (int) diameter,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ Paint circlePaint = new Paint();
+ circlePaint.setColor(Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.materialColorTertiaryContainer));
+ circlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ canvas.drawCircle(diameter / 2f, diameter / 2f, diameter / 2f, circlePaint);
+
+ Paint textPaint = new Paint();
+ textPaint.setColor(Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.materialColorOnTertiaryContainer));
+ textPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
+ textPaint.setTextAlign(Paint.Align.LEFT);
+ textPaint.setTextSize(res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_text_size));
+
+ String text = displayName.substring(0, 1).toUpperCase(Locale.getDefault());
+ Rect textRect = new Rect();
+ textPaint.getTextBounds(text, 0, text.length(), textRect);
+
+ float textX = diameter / 2f - textRect.width() / 2f - textRect.left;
+ float textY = diameter / 2f + textRect.height() / 2f - textRect.bottom;
+ canvas.drawText(text, textX, textY, textPaint);
+
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
+
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
diff --git a/src/com/android/settings/notification/modes/ZenHelperBackend.java b/src/com/android/settings/notification/modes/ZenHelperBackend.java
index 4136c2210bc..a2c3578af07 100644
--- a/src/com/android/settings/notification/modes/ZenHelperBackend.java
+++ b/src/com/android/settings/notification/modes/ZenHelperBackend.java
@@ -21,15 +21,22 @@ import android.app.INotificationManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.ServiceManager;
import android.provider.ContactsContract;
import android.service.notification.ConversationChannelWrapper;
import android.util.Log;
-import androidx.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import com.android.settings.R;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@@ -75,45 +82,99 @@ class ZenHelperBackend {
}
@SuppressWarnings("unchecked")
- ParceledListSlice getConversations(boolean onlyImportant) {
+ ImmutableList getImportantConversations() {
try {
- return mInm.getConversations(onlyImportant);
+ ImmutableList.Builder list = new ImmutableList.Builder<>();
+ ParceledListSlice parceledList = mInm.getConversations(
+ /* onlyImportant= */ true);
+ if (parceledList != null) {
+ for (ConversationChannelWrapper conversation : parceledList.getList()) {
+ if (!conversation.getNotificationChannel().isDemoted()) {
+ list.add(conversation);
+ }
+ }
+ }
+ return list.build();
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
- return ParceledListSlice.emptyList();
+ return ImmutableList.of();
}
}
- List getStarredContacts() {
+ record Contact(long id, @Nullable String displayName, @Nullable Uri photoUri) { }
+
+ ImmutableList getAllContacts() {
+ try (Cursor cursor = queryAllContactsData()) {
+ return getContactsFromCursor(cursor);
+ }
+ }
+
+ ImmutableList getStarredContacts() {
try (Cursor cursor = queryStarredContactsData()) {
- return getStarredContacts(cursor);
+ return getContactsFromCursor(cursor);
}
}
- @VisibleForTesting
- List getStarredContacts(Cursor cursor) {
- List starredContacts = new ArrayList<>();
+ private ImmutableList getContactsFromCursor(Cursor cursor) {
+ ImmutableList.Builder list = new ImmutableList.Builder<>();
if (cursor != null && cursor.moveToFirst()) {
do {
- String contact = cursor.getString(0);
- starredContacts.add(contact != null ? contact :
- mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
-
+ long id = cursor.getLong(0);
+ String name = Strings.emptyToNull(cursor.getString(1));
+ String photoUriStr = cursor.getString(2);
+ Uri photoUri = !Strings.isNullOrEmpty(photoUriStr) ? Uri.parse(photoUriStr) : null;
+ list.add(new Contact(id, name, photoUri));
} while (cursor.moveToNext());
}
- return starredContacts;
+ return list.build();
}
+ int getAllContactsCount() {
+ try (Cursor cursor = queryAllContactsData()) {
+ return cursor != null ? cursor.getCount() : 0;
+ }
+ }
+
+ private static final String[] CONTACTS_PROJECTION = new String[] {
+ ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
+ ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
+ };
+
private Cursor queryStarredContactsData() {
- return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
- new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
- ContactsContract.Data.STARRED + "=1", null,
- ContactsContract.Data.TIMES_CONTACTED);
+ return mContext.getContentResolver().query(
+ ContactsContract.Contacts.CONTENT_URI,
+ CONTACTS_PROJECTION,
+ /* selection= */ ContactsContract.Data.STARRED + "=1", /* selectionArgs= */ null,
+ /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
}
- Cursor queryAllContactsData() {
- return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
- new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
- null, null, null);
+ private Cursor queryAllContactsData() {
+ return mContext.getContentResolver().query(
+ ContactsContract.Contacts.CONTENT_URI,
+ CONTACTS_PROJECTION,
+ /* selection= */ null, /* selectionArgs= */ null,
+ /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
+ }
+
+ @NonNull
+ Drawable getContactPhoto(Contact contact) {
+ if (contact.photoUri != null) {
+ try (InputStream is = mContext.getContentResolver().openInputStream(contact.photoUri)) {
+ if (is != null) {
+ RoundedBitmapDrawable rbd = RoundedBitmapDrawableFactory.create(
+ mContext.getResources(), is);
+ if (rbd != null && rbd.getBitmap() != null) {
+ rbd.setCircular(true);
+ return rbd;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Couldn't load photo for " + contact, e);
+ }
+ }
+
+ // Fall back to a monogram if no picture.
+ return IconUtil.makeContactMonogram(mContext, contact.displayName);
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index fce48afe443..206ad42bc7b 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -87,6 +87,6 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
}
}
return new CircularIconSet<>(icons.build(),
- iconResId -> IconUtil.makeSoundIcon(mContext, iconResId));
+ iconResId -> IconUtil.makeCircularIconPreferenceItem(mContext, iconResId));
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 2a614188801..762cdd57ca6 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -17,25 +17,67 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.graphics.drawable.Drawable;
+import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.ZenPolicy;
+import android.service.notification.ZenPolicy.ConversationSenders;
+import android.service.notification.ZenPolicy.PeopleType;
+import android.util.IconDrawableFactory;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.preference.Preference;
+import com.android.settings.R;
+import com.android.settings.notification.modes.ZenHelperBackend.Contact;
+import com.android.settingslib.notification.ConversationIconFactory;
import com.android.settingslib.notification.modes.ZenMode;
+import com.google.common.collect.ImmutableList;
+
+import java.util.function.Function;
+
/**
- * Preference with a link and summary about what calls and messages can break through the mode
+ * Preference with a link and summary about what calls and messages can break through the mode,
+ * and icons representing those people.
*/
class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
+ private final ZenHelperBackend mHelperBackend;
+ private final ConversationIconFactory mConversationIconFactory;
- public ZenModePeopleLinkPreferenceController(Context context, String key,
+ ZenModePeopleLinkPreferenceController(Context context, String key,
ZenHelperBackend helperBackend) {
+ this(context, key, helperBackend,
+ new ConversationIconFactory(context,
+ context.getSystemService(LauncherApps.class),
+ context.getPackageManager(),
+ IconDrawableFactory.newInstance(context, false),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_circular_icon_diameter)));
+ }
+
+ @VisibleForTesting
+ ZenModePeopleLinkPreferenceController(Context context, String key,
+ ZenHelperBackend helperBackend, ConversationIconFactory conversationIconFactory) {
super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
+ mHelperBackend = helperBackend;
+ mConversationIconFactory = conversationIconFactory;
}
@Override
@@ -50,8 +92,104 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
zenMode.getId(), 0).toIntent());
- preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
- // TODO: b/346551087 - Show people icons
- ((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
+ preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
+ ((CircularIconsPreference) preference).displayIcons(getPeopleIcons(zenMode.getPolicy()));
+ }
+
+ // Represents "Either".
+ record PeopleItem(@Nullable Contact contact,
+ @Nullable ConversationChannelWrapper conversation) {
+
+ PeopleItem(@NonNull Contact contact) {
+ this(contact, null);
+ }
+
+ PeopleItem(@NonNull ConversationChannelWrapper conversation) {
+ this(null, conversation);
+ }
+
+ }
+
+ private CircularIconSet> getPeopleIcons(ZenPolicy policy) {
+ if (getCallersOrMessagesAllowed(policy) == PEOPLE_TYPE_ANYONE) {
+ return new CircularIconSet<>(
+ ImmutableList.of(IconUtil.makeCircularIconPreferenceItem(mContext,
+ R.drawable.ic_zen_mode_people_all)),
+ Function.identity());
+ }
+
+ ImmutableList.Builder peopleItems = ImmutableList.builder();
+ fetchContactsAllowed(policy, peopleItems);
+ fetchConversationsAllowed(policy, peopleItems);
+ return new CircularIconSet<>(peopleItems.build(), this::loadPeopleIcon);
+ }
+
+ /**
+ * Adds {@link PeopleItem} entries corresponding to the set of people (contacts) who can
+ * break through via either call OR message.
+ */
+ private void fetchContactsAllowed(ZenPolicy policy,
+ ImmutableList.Builder peopleItems) {
+ @PeopleType int peopleAllowed = getCallersOrMessagesAllowed(policy);
+
+ ImmutableList contactsAllowed = ImmutableList.of();
+ if (peopleAllowed == PEOPLE_TYPE_CONTACTS) {
+ contactsAllowed = mHelperBackend.getAllContacts();
+ } else if (peopleAllowed == PEOPLE_TYPE_STARRED) {
+ contactsAllowed = mHelperBackend.getStarredContacts();
+ }
+
+ for (Contact contact : contactsAllowed) {
+ peopleItems.add(new PeopleItem(contact));
+ }
+ }
+
+ /**
+ * Adds {@link PeopleItem} entries corresponding to the set of conversation channels that can
+ * break through.
+ */
+ private void fetchConversationsAllowed(ZenPolicy policy,
+ ImmutableList.Builder peopleItems) {
+ @ConversationSenders int conversationSendersAllowed =
+ policy.getPriorityCategoryConversations() == STATE_ALLOW
+ ? policy.getPriorityConversationSenders()
+ : CONVERSATION_SENDERS_NONE;
+ ImmutableList conversationsAllowed = ImmutableList.of();
+ if (conversationSendersAllowed == CONVERSATION_SENDERS_ANYONE) {
+ // TODO: b/354658240 - Need to handle CONVERSATION_SENDERS_ANYONE?
+ return;
+ } else if (conversationSendersAllowed == CONVERSATION_SENDERS_IMPORTANT) {
+ conversationsAllowed = mHelperBackend.getImportantConversations();
+ }
+
+ for (ConversationChannelWrapper conversation : conversationsAllowed) {
+ peopleItems.add(new PeopleItem(conversation));
+ }
+ }
+
+ /** Returns the broadest set of people who can call OR message. */
+ private @PeopleType int getCallersOrMessagesAllowed(ZenPolicy policy) {
+ @PeopleType int callersAllowed = policy.getPriorityCategoryCalls() == STATE_ALLOW
+ ? policy.getPriorityCallSenders() : PEOPLE_TYPE_NONE;
+ @PeopleType int messagesAllowed = policy.getPriorityCategoryMessages() == STATE_ALLOW
+ ? policy.getPriorityMessageSenders() : PEOPLE_TYPE_NONE;
+
+ // Order is ANYONE -> CONTACTS -> STARRED -> NONE, so just taking the minimum works.
+ return Math.min(callersAllowed, messagesAllowed);
+ }
+
+ @WorkerThread
+ private Drawable loadPeopleIcon(PeopleItem peopleItem) {
+ if (peopleItem.contact != null) {
+ return mHelperBackend.getContactPhoto(peopleItem.contact);
+ } else if (peopleItem.conversation != null) {
+ return mConversationIconFactory.getConversationDrawable(
+ peopleItem.conversation.getShortcutInfo(),
+ peopleItem.conversation.getPkg(),
+ peopleItem.conversation.getUid(),
+ /* important= */ true);
+ } else {
+ throw new IllegalArgumentException("Neither contact nor conversation!");
+ }
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index 0f9323d81fc..32c6a9881f0 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -30,10 +30,8 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
import android.icu.text.MessageFormat;
import android.provider.Contacts;
-import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenPolicy;
import android.view.View;
@@ -167,17 +165,7 @@ class ZenModePrioritySendersPreferenceController
}
private void updateChannelCounts() {
- ParceledListSlice impConversations =
- mHelperBackend.getConversations(true);
- int numImportantConversations = 0;
- if (impConversations != null) {
- for (ConversationChannelWrapper conversation : impConversations.getList()) {
- if (!conversation.getNotificationChannel().isDemoted()) {
- numImportantConversations++;
- }
- }
- }
- mNumImportantConversations = numImportantConversations;
+ mNumImportantConversations = mHelperBackend.getImportantConversations().size();
}
private int getPrioritySenders(ZenPolicy policy) {
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index dd3a400e886..1acef20ac6c 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -31,6 +31,7 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
@@ -45,6 +46,8 @@ import android.provider.Settings;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import android.service.notification.ZenPolicy.ConversationSenders;
+import android.service.notification.ZenPolicy.PeopleType;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
@@ -56,6 +59,7 @@ import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.notification.modes.ZenMode;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@@ -365,7 +369,12 @@ class ZenModeSummaryHelper {
}
public String getStarredContactsSummary() {
- List starredContacts = mBackend.getStarredContacts();
+ List starredContacts = mBackend.getStarredContacts().stream()
+ .map(ZenHelperBackend.Contact::displayName)
+ .map(name -> Strings.isNullOrEmpty(name)
+ ? mContext.getString(R.string.zen_mode_starred_contacts_empty_name)
+ : name)
+ .toList();
int numStarredContacts = starredContacts.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_starred_contacts_summary_contacts),
@@ -389,26 +398,32 @@ class ZenModeSummaryHelper {
mContext.getString(R.string.zen_mode_contacts_count),
Locale.getDefault());
Map args = new HashMap<>();
- args.put("count", mBackend.queryAllContactsData().getCount());
+ args.put("count", mBackend.getAllContactsCount());
return msgFormat.format(args);
}
- public String getPeopleSummary(ZenMode zenMode) {
- final int callersAllowed = zenMode.getPolicy().getPriorityCallSenders();
- final int messagesAllowed = zenMode.getPolicy().getPriorityMessageSenders();
- final int conversationsAllowed = zenMode.getPolicy().getPriorityConversationSenders();
+ public String getPeopleSummary(ZenPolicy policy) {
+ @PeopleType int callersAllowed = policy.getPriorityCategoryCalls() == STATE_ALLOW
+ ? policy.getPriorityCallSenders() : PEOPLE_TYPE_NONE;
+ @PeopleType int messagesAllowed = policy.getPriorityCategoryMessages() == STATE_ALLOW
+ ? policy.getPriorityMessageSenders() : PEOPLE_TYPE_NONE;
+ @ConversationSenders int conversationsAllowed =
+ policy.getPriorityCategoryConversations() == STATE_ALLOW
+ ? policy.getPriorityConversationSenders()
+ : CONVERSATION_SENDERS_NONE;
final boolean areRepeatCallersAllowed =
- zenMode.getPolicy().isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
+ policy.isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
if (callersAllowed == PEOPLE_TYPE_ANYONE
&& messagesAllowed == PEOPLE_TYPE_ANYONE
&& conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
- return mContext.getResources().getString(R.string.zen_mode_people_all);
+ return mContext.getString(R.string.zen_mode_people_all);
} else if (callersAllowed == PEOPLE_TYPE_NONE
&& messagesAllowed == PEOPLE_TYPE_NONE
- && conversationsAllowed == CONVERSATION_SENDERS_NONE
- && !areRepeatCallersAllowed) {
- return mContext.getResources().getString(R.string.zen_mode_people_none);
+ && conversationsAllowed == CONVERSATION_SENDERS_NONE) {
+ return mContext.getString(
+ areRepeatCallersAllowed ? R.string.zen_mode_people_repeat_callers
+ : R.string.zen_mode_people_none);
} else {
return mContext.getResources().getString(R.string.zen_mode_people_some);
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index 0db26c3cae4..8ec980d30aa 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -16,17 +16,47 @@
package com.android.settings.notification.modes;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Flags;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.ZenPolicy;
+import android.view.LayoutInflater;
+import android.view.View;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.notification.modes.ZenHelperBackend.Contact;
+import com.android.settingslib.notification.ConversationIconFactory;
import com.android.settingslib.notification.modes.TestModeBuilder;
+import com.android.settingslib.notification.modes.ZenMode;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Rule;
@@ -34,39 +64,160 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.Collection;
+
+@EnableFlags(Flags.FLAG_MODES_UI)
@RunWith(RobolectricTestRunner.class)
public final class ZenModePeopleLinkPreferenceControllerTest {
private ZenModePeopleLinkPreferenceController mController;
+ private CircularIconsPreference mPreference;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
- @Mock
- private ZenHelperBackend mHelperBackend;
+ @Mock private ZenHelperBackend mHelperBackend;
+ @Mock private ConversationIconFactory mConversationIconFactory;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
-
mContext = RuntimeEnvironment.application;
+ CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+ mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+
+ // Ensure the preference view is bound & measured (needed to add icons).
+ View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+ null);
+ preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+ PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+ mPreference.onBindViewHolder(holder);
mController = new ZenModePeopleLinkPreferenceController(
- mContext, "something", mHelperBackend);
+ mContext, "something", mHelperBackend, mConversationIconFactory);
+
+ setUpContacts(ImmutableList.of(), ImmutableList.of());
+ setUpImportantConversations(ImmutableList.of());
+
+ when(mHelperBackend.getContactPhoto(any())).then(
+ (Answer) invocationOnMock -> photoOf(invocationOnMock.getArgument(0)));
+ when(mConversationIconFactory.getConversationDrawable((ShortcutInfo) any(), any(), anyInt(),
+ anyBoolean())).thenReturn(new ColorDrawable(Color.BLACK));
}
@Test
- @EnableFlags(Flags.FLAG_MODES_UI)
- public void testHasSummary() {
- CircularIconsPreference pref = mock(CircularIconsPreference.class);
+ public void updateState_setsSummary() {
+ mController.updateState(mPreference, TestModeBuilder.EXAMPLE);
- mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
+ assertThat(mPreference.getSummary()).isNotNull();
+ assertThat(mPreference.getSummary().toString()).isNotEmpty();
+ }
- verify(pref).setSummary(any());
- verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
+ @Test
+ public void updateState_starredCallsNoMessages_displaysStarredContacts() {
+ setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowMessages(PEOPLE_TYPE_NONE)
+ .build())
+ .build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mPreference.getIcons()).hasSize(2);
+ assertThat(mPreference.getIcons().stream()
+ .map(ColorDrawable.class::cast)
+ .map(d -> d.getColor()).toList())
+ .containsExactly(2, 3).inOrder();
+ }
+
+ @Test
+ public void updateState_starredCallsContactMessages_displaysAllContacts() {
+ setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowMessages(PEOPLE_TYPE_CONTACTS)
+ .build())
+ .build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mPreference.getIcons()).hasSize(4);
+ assertThat(mPreference.getIcons().stream()
+ .map(ColorDrawable.class::cast)
+ .map(d -> d.getColor()).toList())
+ .containsExactly(1, 2, 3, 4).inOrder();
+ }
+
+ @Test
+ public void updateState_anyoneCallsContactMessages_displaysAnyonePlaceholder() {
+ setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_ANYONE)
+ .allowMessages(PEOPLE_TYPE_CONTACTS)
+ .build())
+ .build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mPreference.getIcons()).hasSize(1);
+ verify(mHelperBackend, never()).getContactPhoto(any());
+ }
+
+ @Test
+ public void updateState_noContactsButImportantConversations_displaysConversations() {
+ setUpContacts(ImmutableList.of(), ImmutableList.of());
+ setUpImportantConversations(ImmutableList.of(1, 2, 3));
+ ZenMode mode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_CONTACTS)
+ .allowMessages(PEOPLE_TYPE_CONTACTS)
+ .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+ .build())
+ .build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mPreference.getIcons()).hasSize(3);
+ verify(mConversationIconFactory, times(3)).getConversationDrawable((ShortcutInfo) any(),
+ any(), anyInt(), anyBoolean());
+ }
+
+ private void setUpContacts(Collection allIds, Collection starredIds) {
+ when(mHelperBackend.getAllContacts()).thenReturn(ImmutableList.copyOf(
+ allIds.stream()
+ .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id)))
+ .toList()));
+
+ when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.copyOf(
+ starredIds.stream()
+ .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id)))
+ .toList()));
+ }
+
+ private void setUpImportantConversations(Collection ids) {
+ when(mHelperBackend.getImportantConversations()).thenReturn(ImmutableList.copyOf(
+ ids.stream()
+ .map(id -> {
+ ConversationChannelWrapper channel = new ConversationChannelWrapper();
+ channel.setNotificationChannel(
+ new NotificationChannel(id.toString(), id.toString(),
+ NotificationManager.IMPORTANCE_DEFAULT));
+ return channel;
+ })
+ .toList()));
+ }
+
+ private static ColorDrawable photoOf(Contact contact) {
+ return new ColorDrawable((int) contact.id());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
index 944d4325644..64de1418ad6 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
@@ -35,13 +35,11 @@ import static com.android.settings.notification.modes.ZenModePrioritySendersPref
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Flags;
import android.content.Context;
-import android.database.Cursor;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
@@ -56,6 +54,8 @@ import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -105,22 +105,11 @@ public final class ZenModePrioritySendersPreferenceControllerTest {
mPreferenceScreen.addPreference(mCallsPrefCategory);
mPreferenceScreen.addPreference(mMessagesPrefCategory);
- Cursor cursor = mock(Cursor.class);
- when(cursor.getCount()).thenReturn(1);
- when(mHelperBackend.queryAllContactsData()).thenReturn(cursor);
- }
-
- // Makes a preference with the provided key and whether it's a checkbox with
- // mSelectorClickListener as the onClickListener set.
- private SelectorWithWidgetPreference makePreference(
- String key, boolean isCheckbox, boolean isMessages) {
- final SelectorWithWidgetPreference pref =
- new SelectorWithWidgetPreference(mContext, isCheckbox);
- pref.setKey(key);
- pref.setOnClickListener(
- isMessages ? mMessagesController.mSelectorClickListener
- : mCallsController.mSelectorClickListener);
- return pref;
+ when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.of());
+ when(mHelperBackend.getAllContacts()).thenReturn(
+ ImmutableList.of(new ZenHelperBackend.Contact(1, "The only contact", null)));
+ when(mHelperBackend.getAllContactsCount()).thenReturn(1);
+ when(mHelperBackend.getImportantConversations()).thenReturn(ImmutableList.of());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
index 672a0d74355..a7257f53705 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
@@ -89,31 +89,38 @@ public class ZenModesSummaryHelperTest {
@Test
public void getPeopleSummary_noOne() {
- ZenMode zenMode = new TestModeBuilder()
- .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
- .build();
+ ZenPolicy policy = new ZenPolicy.Builder().disallowAllSounds().build();
- assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("No one can interrupt");
+ assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("No one can interrupt");
}
@Test
public void getPeopleSummary_some() {
- ZenMode zenMode = new TestModeBuilder()
- .setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build())
+ ZenPolicy policy = new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build();
+
+ assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("Some people can interrupt");
+ }
+
+ @Test
+ public void getPeopleSummary_onlyRepeatCallers() {
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowRepeatCallers(true)
.build();
- assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("Some people can interrupt");
+ assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo(
+ "Repeat callers can interrupt");
}
@Test
public void getPeopleSummary_all() {
- ZenMode zenMode = new TestModeBuilder()
- .setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_ANYONE).
- allowConversations(CONVERSATION_SENDERS_ANYONE)
- .allowMessages(PEOPLE_TYPE_ANYONE).build())
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_ANYONE)
+ .allowConversations(CONVERSATION_SENDERS_ANYONE)
+ .allowMessages(PEOPLE_TYPE_ANYONE)
.build();
- assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("All people can interrupt");
+ assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("All people can interrupt");
}
@Test