Files
packages_apps_Settings/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
Matías Hernández 0baf70a4b3 Exclude implicit modes from the summary of the Modes entry in Settings
They are called "Do Not Disturb (App)" which places alphabetically near the top (in English). This can often result in a summary like "DND, Bedtime, DND (App)", which looks silly.

Fixes: 379089451
Test: atest ZenModeSummaryHelperTest
Flag: android.app.modes_ui
Change-Id: Id41caee09cca874297ca12d2ac934aed3f9a43ab
2024-11-14 15:33:54 +00:00

528 lines
24 KiB
Java

/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS;
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_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;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_STATUS_BAR;
import android.content.Context;
import android.icu.text.MessageFormat;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import android.service.notification.ZenPolicy.ConversationSenders;
import android.service.notification.ZenPolicy.PeopleType;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
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;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
class ZenModeSummaryHelper {
private final Context mContext;
private final ZenHelperBackend mBackend;
ZenModeSummaryHelper(Context context, ZenHelperBackend backend) {
mContext = context;
mBackend = backend;
}
private static final int[] ALL_PRIORITY_CATEGORIES = {
PRIORITY_CATEGORY_ALARMS,
PRIORITY_CATEGORY_MEDIA,
PRIORITY_CATEGORY_SYSTEM,
PRIORITY_CATEGORY_MESSAGES,
PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_CALLS,
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<String> enabledCategories = getEnabledCategories(
zenMode.getPolicy(),
OTHER_SOUND_CATEGORIES::contains,
true);
int numCategories = enabledCategories.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_other_sounds_summary),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numCategories);
if (numCategories >= 1) {
args.put("sound_category_1", enabledCategories.get(0));
if (numCategories >= 2) {
args.put("sound_category_2", enabledCategories.get(1));
if (numCategories == 3) {
args.put("sound_category_3", enabledCategories.get(2));
}
}
}
return msgFormat.format(args);
}
String getCallsSettingSummary(ZenMode zenMode) {
List<String> enabledCategories = getEnabledCategories(zenMode.getPolicy(),
category -> PRIORITY_CATEGORY_CALLS == category
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_none_calls);
} else if (numCategories == 1) {
return mContext.getString(R.string.zen_mode_calls_summary_one,
enabledCategories.get(0));
} else {
return mContext.getString(R.string.zen_mode_calls_summary_two,
enabledCategories.get(0),
enabledCategories.get(1));
}
}
String getMessagesSettingSummary(ZenPolicy policy) {
if (policy.getPriorityCategoryMessages() == STATE_ALLOW
&& policy.getPriorityMessageSenders() == PEOPLE_TYPE_ANYONE) {
// Messages=anyone means anyone. Even if conversation senders is specially configured,
// saying "Anyone and priority conversations" 1) makes no sense and 2) is incorrect
// because conversations WILL get through by virtue of also being messages.
return mContext.getString(R.string.zen_mode_from_anyone);
}
List<String> enabledCategories = getEnabledCategories(policy,
category -> PRIORITY_CATEGORY_MESSAGES == category
|| PRIORITY_CATEGORY_CONVERSATIONS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_none_messages);
} else if (numCategories == 1) {
return enabledCategories.get(0);
} else {
// While this string name seems like a slight misnomer: it's borrowing the analogous
// calls-summary functionality to combine two permissions.
return mContext.getString(R.string.zen_mode_calls_summary_two,
enabledCategories.get(0),
enabledCategories.get(1));
}
}
String getBlockedEffectsSummary(ZenMode zenMode) {
List<Integer> relevantVisualEffects = new ArrayList<>();
relevantVisualEffects.add(VISUAL_EFFECT_FULL_SCREEN_INTENT);
relevantVisualEffects.add(VISUAL_EFFECT_PEEK);
relevantVisualEffects.add(VISUAL_EFFECT_STATUS_BAR);
relevantVisualEffects.add(VISUAL_EFFECT_BADGE);
relevantVisualEffects.add(VISUAL_EFFECT_AMBIENT);
relevantVisualEffects.add(VISUAL_EFFECT_NOTIFICATION_LIST);
if (mContext.getResources()
.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
relevantVisualEffects.add(VISUAL_EFFECT_LIGHTS);
}
if (shouldShowAllVisualEffects(zenMode.getPolicy(), relevantVisualEffects)) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_muted);
} else if (shouldHideAllVisualEffects(zenMode.getPolicy(), relevantVisualEffects)) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_hidden);
} else {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_custom);
}
}
private boolean shouldShowAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects) {
for (int i = 0; i < relevantEffects.size(); i++) {
if (!policy.isVisualEffectAllowed(relevantEffects.get(i), false)) {
return false;
}
}
return true;
}
private boolean shouldHideAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects) {
for (int i = 0; i < relevantEffects.size(); i++) {
if (policy.isVisualEffectAllowed(relevantEffects.get(i), false)) {
return false;
}
}
return true;
}
String getDisplayEffectsSummary(ZenMode zenMode) {
boolean isFirst = true;
List<String> enabledEffects = new ArrayList<>();
if (!zenMode.getPolicy().shouldShowAllVisualEffects()
&& zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL) {
enabledEffects.add(getBlockedEffectsSummary(zenMode));
isFirst = false;
}
ZenDeviceEffects currEffects = zenMode.getDeviceEffects();
if (currEffects.shouldDisplayGrayscale()) {
if (isFirst) {
enabledEffects.add(mContext.getString(R.string.mode_grayscale_title));
} else {
enabledEffects.add(mContext.getString(
R.string.mode_grayscale_title_secondary_list));
}
isFirst = false;
}
if (currEffects.shouldSuppressAmbientDisplay()) {
if (isFirst) {
enabledEffects.add(mContext.getString(R.string.mode_aod_title));
} else {
enabledEffects.add(mContext.getString(
R.string.mode_aod_title_secondary_list));
}
isFirst = false;
}
if (currEffects.shouldDimWallpaper()) {
if (isFirst) {
enabledEffects.add(mContext.getString(R.string.mode_wallpaper_title));
} else {
enabledEffects.add(mContext.getString(
R.string.mode_wallpaper_title_secondary_list));
}
isFirst = false;
}
if (currEffects.shouldUseNightMode()) {
if (isFirst) {
enabledEffects.add(mContext.getString(R.string.mode_dark_theme_title));
} else {
enabledEffects.add(mContext.getString(
R.string.mode_dark_theme_title_secondary_list));
}
isFirst = false;
}
int numCategories = enabledEffects.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.mode_display_settings_summary),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numCategories);
if (numCategories >= 1) {
args.put("effect_1", enabledEffects.get(0));
if (numCategories >= 2) {
args.put("effect_2", enabledEffects.get(1));
if (numCategories == 3) {
args.put("effect_3", enabledEffects.get(2));
}
}
}
return msgFormat.format(args);
}
private List<String> getEnabledCategories(ZenPolicy policy,
Predicate<Integer> filteredCategories, boolean capitalizeFirstInList) {
List<String> enabledCategories = new ArrayList<>();
for (int category : ALL_PRIORITY_CATEGORIES) {
boolean isFirst = capitalizeFirstInList && enabledCategories.isEmpty();
if (filteredCategories.test(category) && policy.isCategoryAllowed(category, false)) {
if (category == PRIORITY_CATEGORY_REPEAT_CALLERS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CALLS, false)
&& policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
continue;
}
// For conversations, only the "all/priority conversations" settings are relevant;
// any other setting is subsumed by the messages-specific messaging.
if (category == PRIORITY_CATEGORY_CONVERSATIONS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CONVERSATIONS, false)
&& policy.getPriorityConversationSenders() != CONVERSATION_SENDERS_ANYONE
&& policy.getPriorityConversationSenders()
!= CONVERSATION_SENDERS_IMPORTANT) {
continue;
}
enabledCategories.add(getCategory(category, policy, isFirst));
}
}
return enabledCategories;
}
private String getCategory(int category, ZenPolicy policy, boolean isFirst) {
if (category == PRIORITY_CATEGORY_ALARMS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_alarms_list_first);
} else {
return mContext.getString(R.string.zen_mode_alarms_list);
}
} else if (category == PRIORITY_CATEGORY_MEDIA) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_media_list_first);
} else {
return mContext.getString(R.string.zen_mode_media_list);
}
} else if (category == PRIORITY_CATEGORY_SYSTEM) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_system_list_first);
} else {
return mContext.getString(R.string.zen_mode_system_list);
}
} else if (category == PRIORITY_CATEGORY_MESSAGES) {
if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_ANYONE) {
return mContext.getString(R.string.zen_mode_from_anyone);
} else if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_CONTACTS) {
return mContext.getString(R.string.zen_mode_from_contacts);
} else {
return mContext.getString(R.string.zen_mode_from_starred);
}
} else if (category == PRIORITY_CATEGORY_CONVERSATIONS) {
if (policy.getPriorityConversationSenders() == CONVERSATION_SENDERS_IMPORTANT) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_important_conversations);
} else {
return mContext.getString(
R.string.zen_mode_from_important_conversations_second);
}
} else if (policy.getPriorityConversationSenders() == CONVERSATION_SENDERS_ANYONE) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_all_conversations);
} else {
return mContext.getString(R.string.zen_mode_from_all_conversations_second);
}
}
} else if (category == PRIORITY_CATEGORY_EVENTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_events_list_first);
} else {
return mContext.getString(R.string.zen_mode_events_list);
}
} else if (category == PRIORITY_CATEGORY_REMINDERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_reminders_list_first);
} else {
return mContext.getString(R.string.zen_mode_reminders_list);
}
} else if (category == PRIORITY_CATEGORY_CALLS) {
if (policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_anyone);
}
return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.getPriorityCallSenders() == PEOPLE_TYPE_CONTACTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_contacts);
}
return mContext.getString(R.string.zen_mode_contacts_callers);
} else {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_starred);
}
return mContext.getString(R.string.zen_mode_starred_callers);
}
} else if (category == PRIORITY_CATEGORY_REPEAT_CALLERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_repeat_callers);
} else {
return mContext.getString(R.string.zen_mode_repeat_callers_list);
}
}
return "";
}
public String getStarredContactsSummary() {
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),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numStarredContacts);
if (numStarredContacts >= 1) {
args.put("contact_1", starredContacts.get(0));
if (numStarredContacts >= 2) {
args.put("contact_2", starredContacts.get(1));
if (numStarredContacts == 3) {
args.put("contact_3", starredContacts.get(2));
}
}
}
return msgFormat.format(args);
}
public String getContactsNumberSummary() {
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_contacts_count),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", mBackend.getAllContactsCount());
return msgFormat.format(args);
}
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 =
policy.isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
if (callersAllowed == PEOPLE_TYPE_ANYONE
&& messagesAllowed == PEOPLE_TYPE_ANYONE
&& conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
return mContext.getString(R.string.zen_mode_people_all);
} else if (callersAllowed == PEOPLE_TYPE_NONE
&& messagesAllowed == PEOPLE_TYPE_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);
}
}
/**
* Generates a summary to display under the top level "Apps" preference for a mode, based
* on the given mode and provided set of apps.
*/
public @NonNull String getAppsSummary(@NonNull ZenMode zenMode,
@Nullable List<AppEntry> appsBypassing) {
if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_PRIORITY) {
return formatAppsList(appsBypassing);
} else if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
return mContext.getResources().getString(R.string.zen_mode_apps_none_apps);
}
return "";
}
/**
* Generates a formatted string declaring which apps can interrupt in the style of
* "App, App2, and 4 more can interrupt."
* Apps selected for explicit mention are picked in order from the provided list.
*/
@VisibleForTesting
public @NonNull String formatAppsList(@Nullable List<AppEntry> appsBypassingDnd) {
if (appsBypassingDnd == null) {
return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps);
}
List<String> appNames = appsBypassingDnd.stream().limit(3)
.map(app -> {
String appName = BidiFormatter.getInstance().unicodeWrap(app.label);
if (app.isManagedProfile()) {
appName = mContext.getString(R.string.zen_mode_apps_work_app, appName);
}
return appName;
})
.toList();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_apps_subtext),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", appsBypassingDnd.size());
if (appNames.size() >= 1) {
args.put("app_1", appNames.get(0));
if (appNames.size() >= 2) {
args.put("app_2", appNames.get(1));
if (appNames.size() == 3) {
args.put("app_3", appNames.get(2));
}
}
}
return msgFormat.format(args);
}
String getModesSummary(List<ZenMode> modes) {
List<ZenMode> activeModes = modes.stream().filter(ZenMode::isActive).toList();
if (!activeModes.isEmpty()) {
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_modes_summary_some_active),
Locale.getDefault());
return buildModesSummary(msgFormat, activeModes);
} else {
List<ZenMode> modesExcludingImplicit = modes.stream()
.filter(m -> m.getKind() != ZenMode.Kind.IMPLICIT)
.toList();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_modes_summary),
Locale.getDefault());
return buildModesSummary(msgFormat, modesExcludingImplicit);
}
}
private static String buildModesSummary(MessageFormat msgFormat, List<ZenMode> modes) {
Map<String, Object> args = new HashMap<>();
args.put("count", modes.size());
if (modes.size() >= 1) {
args.put("mode_1", modes.get(0).getName());
if (modes.size() >= 2) {
args.put("mode_2", modes.get(1).getName());
if (modes.size() >= 3) {
args.put("mode_3", modes.get(2).getName());
}
}
}
return msgFormat.format(args);
}
}