Merge changes Iceadf4a1,If2b6b06b into main
* changes: Final (?) touches to people/apps/sound circles Show icons for allowed contacts
This commit is contained in:
committed by
Android (Google) Code Review
commit
6e9555e604
@@ -20,8 +20,10 @@ import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.google.common.base.Equivalence;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@@ -29,6 +31,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -67,8 +70,25 @@ class CircularIconSet<T> {
|
||||
return MoreObjects.toStringHelper(this).add("items", mItems).toString();
|
||||
}
|
||||
|
||||
boolean hasSameItemsAs(CircularIconSet<?> other) {
|
||||
return other != null && this.mItems.equals(other.mItems);
|
||||
@SuppressWarnings("unchecked")
|
||||
<OtherT> boolean hasSameItemsAs(CircularIconSet<OtherT> other,
|
||||
@Nullable Equivalence<OtherT> equivalence) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (equivalence == null) {
|
||||
return mItems.equals(other.mItems);
|
||||
}
|
||||
// Check that types match before applying equivalence (statically unsafe). :(
|
||||
Optional<Class<?>> thisItemClass = this.mItems.stream().findFirst().map(T::getClass);
|
||||
Optional<Class<?>> otherItemClass = other.mItems.stream().findFirst().map(OtherT::getClass);
|
||||
if (!thisItemClass.equals(otherItemClass)) {
|
||||
return false;
|
||||
}
|
||||
return equivalence.pairwise().equivalent((Iterable<OtherT>) this.mItems, other.mItems);
|
||||
}
|
||||
|
||||
int size() {
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.android.settings.R;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import com.google.common.base.Equivalence;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -105,8 +106,12 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
}
|
||||
}
|
||||
|
||||
void displayIcons(CircularIconSet<?> iconSet) {
|
||||
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet)) {
|
||||
<T> void displayIcons(CircularIconSet<T> iconSet) {
|
||||
displayIcons(iconSet, null);
|
||||
}
|
||||
|
||||
<T> void displayIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
|
||||
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
|
||||
return;
|
||||
}
|
||||
mIconSet = iconSet;
|
||||
@@ -189,7 +194,6 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
}
|
||||
// ... 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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<ConversationChannelWrapper> getConversations(boolean onlyImportant) {
|
||||
ImmutableList<ConversationChannelWrapper> getImportantConversations() {
|
||||
try {
|
||||
return mInm.getConversations(onlyImportant);
|
||||
ImmutableList.Builder<ConversationChannelWrapper> list = new ImmutableList.Builder<>();
|
||||
ParceledListSlice<ConversationChannelWrapper> 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<String> getStarredContacts() {
|
||||
record Contact(long id, @Nullable String displayName, @Nullable Uri photoUri) { }
|
||||
|
||||
ImmutableList<Contact> getAllContacts() {
|
||||
try (Cursor cursor = queryAllContactsData()) {
|
||||
return getContactsFromCursor(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableList<Contact> getStarredContacts() {
|
||||
try (Cursor cursor = queryStarredContactsData()) {
|
||||
return getStarredContacts(cursor);
|
||||
return getContactsFromCursor(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<String> getStarredContacts(Cursor cursor) {
|
||||
List<String> starredContacts = new ArrayList<>();
|
||||
private ImmutableList<Contact> getContactsFromCursor(Cursor cursor) {
|
||||
ImmutableList.Builder<Contact> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
|
||||
import com.google.common.base.Equivalence;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
@@ -47,6 +48,7 @@ import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Preference with a link and summary about what apps can break through the mode
|
||||
@@ -137,7 +139,8 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
|
||||
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
|
||||
|
||||
mPreference.displayIcons(new CircularIconSet<>(apps,
|
||||
app -> Utils.getBadgedIcon(mContext, app.info)));
|
||||
app -> Utils.getBadgedIcon(mContext, app.info)),
|
||||
APP_ENTRY_EQUIVALENCE);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -158,6 +161,19 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
|
||||
.toList());
|
||||
}
|
||||
|
||||
private static final Equivalence<AppEntry> APP_ENTRY_EQUIVALENCE = new Equivalence<>() {
|
||||
@Override
|
||||
protected boolean doEquivalent(@NonNull AppEntry a, @NonNull AppEntry b) {
|
||||
return a.info.uid == b.info.uid
|
||||
&& Objects.equals(a.info.packageName, b.info.packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doHash(@NonNull AppEntry entry) {
|
||||
return Objects.hash(entry.info.uid, entry.info.packageName);
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
final ApplicationsState.Callbacks mAppSessionCallbacks =
|
||||
new ApplicationsState.Callbacks() {
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.service.notification.ZenPolicy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -41,19 +42,13 @@ import java.util.Map;
|
||||
*/
|
||||
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);
|
||||
PRIORITY_CATEGORY_ALARMS, R.drawable.ic_zen_mode_sound_alarms,
|
||||
PRIORITY_CATEGORY_MEDIA, R.drawable.ic_zen_mode_sound_media,
|
||||
PRIORITY_CATEGORY_SYSTEM, R.drawable.ic_zen_mode_sound_system,
|
||||
PRIORITY_CATEGORY_REMINDERS, R.drawable.ic_zen_mode_sound_reminders,
|
||||
PRIORITY_CATEGORY_EVENTS, R.drawable.ic_zen_mode_sound_events);
|
||||
|
||||
private final ZenModeSummaryHelper mSummaryHelper;
|
||||
|
||||
@@ -87,6 +82,6 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
|
||||
}
|
||||
}
|
||||
return new CircularIconSet<>(icons.build(),
|
||||
iconResId -> IconUtil.makeSoundIcon(mContext, iconResId));
|
||||
iconResId -> IconUtil.makeCircularIconPreferenceItem(mContext, iconResId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Contact, ConversationChannelWrapper>".
|
||||
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<PeopleItem> 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<PeopleItem> peopleItems) {
|
||||
@PeopleType int peopleAllowed = getCallersOrMessagesAllowed(policy);
|
||||
|
||||
ImmutableList<Contact> 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<PeopleItem> peopleItems) {
|
||||
@ConversationSenders int conversationSendersAllowed =
|
||||
policy.getPriorityCategoryConversations() == STATE_ALLOW
|
||||
? policy.getPriorityConversationSenders()
|
||||
: CONVERSATION_SENDERS_NONE;
|
||||
ImmutableList<ConversationChannelWrapper> 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ConversationChannelWrapper> 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) {
|
||||
|
||||
@@ -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<String> starredContacts = mBackend.getStarredContacts();
|
||||
List<String> 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<String, Object> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user