1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.notification.modes;
17 
18 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
19 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
20 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
21 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
22 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
23 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
24 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
25 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
26 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS;
27 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
28 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
29 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
30 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
31 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
32 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
33 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
34 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
35 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
36 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
37 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST;
38 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_PEEK;
39 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_STATUS_BAR;
40 
41 import android.content.Context;
42 import android.icu.text.MessageFormat;
43 import android.service.notification.ZenDeviceEffects;
44 import android.service.notification.ZenPolicy;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 
49 import com.android.settings.R;
50 
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.function.Predicate;
59 
60 class ZenModeSummaryHelper {
61 
62     private final Context mContext;
63     private final ZenModesBackend mBackend;
64 
ZenModeSummaryHelper(Context context, ZenModesBackend backend)65     public ZenModeSummaryHelper(Context context, ZenModesBackend backend) {
66         mContext = context;
67         mBackend = backend;
68     }
69 
70     private static final int[] ALL_PRIORITY_CATEGORIES = {
71             PRIORITY_CATEGORY_ALARMS,
72             PRIORITY_CATEGORY_MEDIA,
73             PRIORITY_CATEGORY_SYSTEM,
74             PRIORITY_CATEGORY_MESSAGES,
75             PRIORITY_CATEGORY_CONVERSATIONS,
76             PRIORITY_CATEGORY_EVENTS,
77             PRIORITY_CATEGORY_REMINDERS,
78             PRIORITY_CATEGORY_CALLS,
79             PRIORITY_CATEGORY_REPEAT_CALLERS,
80     };
81 
getOtherSoundCategoriesSummary(ZenMode zenMode)82     String getOtherSoundCategoriesSummary(ZenMode zenMode) {
83         List<String> enabledCategories = getEnabledCategories(
84                 zenMode.getPolicy(),
85                 category -> PRIORITY_CATEGORY_ALARMS == category
86                         || PRIORITY_CATEGORY_MEDIA == category
87                         || PRIORITY_CATEGORY_SYSTEM == category
88                         || PRIORITY_CATEGORY_REMINDERS == category
89                         || PRIORITY_CATEGORY_EVENTS == category,
90                 true);
91         int numCategories = enabledCategories.size();
92         MessageFormat msgFormat = new MessageFormat(
93                 mContext.getString(R.string.zen_mode_other_sounds_summary),
94                 Locale.getDefault());
95         Map<String, Object> args = new HashMap<>();
96         args.put("count", numCategories);
97         if (numCategories >= 1) {
98             args.put("sound_category_1", enabledCategories.get(0));
99             if (numCategories >= 2) {
100                 args.put("sound_category_2", enabledCategories.get(1));
101                 if (numCategories == 3) {
102                     args.put("sound_category_3", enabledCategories.get(2));
103                 }
104             }
105         }
106         return msgFormat.format(args);
107     }
108 
getCallsSettingSummary(ZenMode zenMode)109     String getCallsSettingSummary(ZenMode zenMode) {
110         List<String> enabledCategories = getEnabledCategories(zenMode.getPolicy(),
111                 category -> PRIORITY_CATEGORY_CALLS == category
112                         || PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
113         int numCategories = enabledCategories.size();
114         if (numCategories == 0) {
115             return mContext.getString(R.string.zen_mode_none_calls);
116         } else if (numCategories == 1) {
117             return mContext.getString(R.string.zen_mode_calls_summary_one,
118                     enabledCategories.get(0));
119         } else {
120             return mContext.getString(R.string.zen_mode_calls_summary_two,
121                     enabledCategories.get(0),
122                     enabledCategories.get(1));
123         }
124     }
125 
getMessagesSettingSummary(ZenPolicy policy)126     String getMessagesSettingSummary(ZenPolicy policy) {
127         List<String> enabledCategories = getEnabledCategories(policy,
128                 category -> PRIORITY_CATEGORY_MESSAGES == category
129                         || PRIORITY_CATEGORY_CONVERSATIONS == category, true);
130         int numCategories = enabledCategories.size();
131         if (numCategories == 0) {
132             return mContext.getString(R.string.zen_mode_none_messages);
133         } else if (numCategories == 1) {
134             return enabledCategories.get(0);
135         } else {
136             // While this string name seems like a slight misnomer: it's borrowing the analogous
137             // calls-summary functionality to combine two permissions.
138             return mContext.getString(R.string.zen_mode_calls_summary_two,
139                     enabledCategories.get(0),
140                     enabledCategories.get(1));
141         }
142     }
143 
getBlockedEffectsSummary(ZenMode zenMode)144     String getBlockedEffectsSummary(ZenMode zenMode) {
145         List<Integer> relevantVisualEffects = new ArrayList<>();
146         relevantVisualEffects.add(VISUAL_EFFECT_FULL_SCREEN_INTENT);
147         relevantVisualEffects.add(VISUAL_EFFECT_PEEK);
148         relevantVisualEffects.add(VISUAL_EFFECT_STATUS_BAR);
149         relevantVisualEffects.add(VISUAL_EFFECT_BADGE);
150         relevantVisualEffects.add(VISUAL_EFFECT_AMBIENT);
151         relevantVisualEffects.add(VISUAL_EFFECT_NOTIFICATION_LIST);
152         if (mContext.getResources()
153                 .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
154             relevantVisualEffects.add(VISUAL_EFFECT_LIGHTS);
155         }
156 
157         if (shouldShowAllVisualEffects(zenMode.getPolicy(), relevantVisualEffects)) {
158             return mContext.getResources().getString(
159                     R.string.zen_mode_restrict_notifications_summary_muted);
160         } else if (shouldHideAllVisualEffects(zenMode.getPolicy(), relevantVisualEffects)) {
161             return mContext.getResources().getString(
162                     R.string.zen_mode_restrict_notifications_summary_hidden);
163         } else {
164             return mContext.getResources().getString(
165                     R.string.zen_mode_restrict_notifications_summary_custom);
166         }
167     }
168 
shouldShowAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects)169     private boolean shouldShowAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects) {
170         for (int i = 0; i < relevantEffects.size(); i++) {
171             if (!policy.isVisualEffectAllowed(relevantEffects.get(i), false)) {
172                 return false;
173             }
174         }
175         return true;
176     }
177 
shouldHideAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects)178     private boolean shouldHideAllVisualEffects(ZenPolicy policy, List<Integer> relevantEffects) {
179         for (int i = 0; i < relevantEffects.size(); i++) {
180             if (policy.isVisualEffectAllowed(relevantEffects.get(i), false)) {
181                 return false;
182             }
183         }
184         return true;
185     }
186 
getDisplayEffectsSummary(ZenMode zenMode)187     String getDisplayEffectsSummary(ZenMode zenMode) {
188         boolean isFirst = true;
189         List<String> enabledEffects = new ArrayList<>();
190         if (!zenMode.getPolicy().shouldShowAllVisualEffects()) {
191             enabledEffects.add(getBlockedEffectsSummary(zenMode));
192             isFirst = false;
193         }
194         ZenDeviceEffects currEffects =  zenMode.getRule().getDeviceEffects();
195         if (currEffects != null) {
196             if (currEffects.shouldDisplayGrayscale()) {
197                 if (isFirst) {
198                     enabledEffects.add(mContext.getString(R.string.mode_grayscale_title));
199                 } else {
200                     enabledEffects.add(mContext.getString(
201                             R.string.mode_grayscale_title_secondary_list));
202                 }
203                 isFirst = false;
204             }
205             if (currEffects.shouldSuppressAmbientDisplay()) {
206                 if (isFirst) {
207                     enabledEffects.add(mContext.getString(R.string.mode_aod_title));
208                 } else {
209                     enabledEffects.add(mContext.getString(
210                             R.string.mode_aod_title_secondary_list));
211                 }
212                 isFirst = false;
213             }
214             if (currEffects.shouldDimWallpaper()) {
215                 if (isFirst) {
216                     enabledEffects.add(mContext.getString(R.string.mode_wallpaper_title));
217                 } else {
218                     enabledEffects.add(mContext.getString(
219                             R.string.mode_wallpaper_title_secondary_list));
220                 }
221                 isFirst = false;
222             }
223             if (currEffects.shouldUseNightMode()) {
224                 if (isFirst) {
225                     enabledEffects.add(mContext.getString(R.string.mode_dark_theme_title));
226                 } else {
227                     enabledEffects.add(mContext.getString(
228                             R.string.mode_dark_theme_title_secondary_list));
229                 }
230                 isFirst = false;
231             }
232         }
233 
234         int numCategories = enabledEffects.size();
235         MessageFormat msgFormat = new MessageFormat(
236                 mContext.getString(R.string.mode_display_settings_summary),
237                 Locale.getDefault());
238         Map<String, Object> args = new HashMap<>();
239         args.put("count", numCategories);
240         if (numCategories >= 1) {
241             args.put("effect_1", enabledEffects.get(0));
242             if (numCategories >= 2) {
243                 args.put("effect_2", enabledEffects.get(1));
244                 if (numCategories == 3) {
245                     args.put("effect_3", enabledEffects.get(2));
246                 }
247             }
248         }
249         return msgFormat.format(args);
250     }
251 
getEnabledCategories(ZenPolicy policy, Predicate<Integer> filteredCategories, boolean capitalizeFirstInList)252     private List<String> getEnabledCategories(ZenPolicy policy,
253             Predicate<Integer> filteredCategories, boolean capitalizeFirstInList) {
254         List<String> enabledCategories = new ArrayList<>();
255         for (int category : ALL_PRIORITY_CATEGORIES) {
256             boolean isFirst = capitalizeFirstInList && enabledCategories.isEmpty();
257             if (filteredCategories.test(category) && policy.isCategoryAllowed(category, false)) {
258                 if (category == PRIORITY_CATEGORY_REPEAT_CALLERS
259                         && policy.isCategoryAllowed(PRIORITY_CATEGORY_CALLS, false)
260                         && policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
261                     continue;
262                 }
263 
264                 // For conversations, only the "priority conversations" setting is relevant; any
265                 // other setting is subsumed by the messages-specific messaging.
266                 if (category == PRIORITY_CATEGORY_CONVERSATIONS
267                         && policy.isCategoryAllowed(PRIORITY_CATEGORY_CONVERSATIONS, false)
268                         && policy.getPriorityConversationSenders()
269                         != CONVERSATION_SENDERS_IMPORTANT) {
270                     continue;
271                 }
272 
273                 enabledCategories.add(getCategory(category, policy, isFirst));
274             }
275         }
276         return enabledCategories;
277     }
278 
getCategory(int category, ZenPolicy policy, boolean isFirst)279     private String getCategory(int category, ZenPolicy policy, boolean isFirst) {
280         if (category == PRIORITY_CATEGORY_ALARMS) {
281             if (isFirst) {
282                 return mContext.getString(R.string.zen_mode_alarms_list_first);
283             } else {
284                 return mContext.getString(R.string.zen_mode_alarms_list);
285             }
286         } else if (category == PRIORITY_CATEGORY_MEDIA) {
287             if (isFirst) {
288                 return mContext.getString(R.string.zen_mode_media_list_first);
289             } else {
290                 return mContext.getString(R.string.zen_mode_media_list);
291             }
292         } else if (category == PRIORITY_CATEGORY_SYSTEM) {
293             if (isFirst) {
294                 return mContext.getString(R.string.zen_mode_system_list_first);
295             } else {
296                 return mContext.getString(R.string.zen_mode_system_list);
297             }
298         } else if (category == PRIORITY_CATEGORY_MESSAGES) {
299             if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_ANYONE) {
300                 return mContext.getString(R.string.zen_mode_from_anyone);
301             } else if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_CONTACTS) {
302                 return mContext.getString(R.string.zen_mode_from_contacts);
303             } else {
304                 return mContext.getString(R.string.zen_mode_from_starred);
305             }
306         } else if (category == PRIORITY_CATEGORY_CONVERSATIONS
307                 && policy.getPriorityConversationSenders() == CONVERSATION_SENDERS_IMPORTANT) {
308             if (isFirst) {
309                 return mContext.getString(R.string.zen_mode_from_important_conversations);
310             } else {
311                 return mContext.getString(
312                         R.string.zen_mode_from_important_conversations_second);
313             }
314         } else if (category == PRIORITY_CATEGORY_EVENTS) {
315             if (isFirst) {
316                 return mContext.getString(R.string.zen_mode_events_list_first);
317             } else {
318                 return mContext.getString(R.string.zen_mode_events_list);
319             }
320         } else if (category == PRIORITY_CATEGORY_REMINDERS) {
321             if (isFirst) {
322                 return mContext.getString(R.string.zen_mode_reminders_list_first);
323             } else {
324                 return mContext.getString(R.string.zen_mode_reminders_list);
325             }
326         } else if (category == PRIORITY_CATEGORY_CALLS) {
327             if (policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
328                 if (isFirst) {
329                     return mContext.getString(R.string.zen_mode_from_anyone);
330                 }
331                 return mContext.getString(R.string.zen_mode_all_callers);
332             } else if (policy.getPriorityCallSenders() == PEOPLE_TYPE_CONTACTS) {
333                 if (isFirst) {
334                     return mContext.getString(R.string.zen_mode_from_contacts);
335                 }
336                 return mContext.getString(R.string.zen_mode_contacts_callers);
337             } else {
338                 if (isFirst) {
339                     return mContext.getString(R.string.zen_mode_from_starred);
340                 }
341                 return mContext.getString(R.string.zen_mode_starred_callers);
342             }
343         } else if (category == PRIORITY_CATEGORY_REPEAT_CALLERS) {
344             if (isFirst) {
345                 return mContext.getString(R.string.zen_mode_repeat_callers);
346             } else {
347                 return mContext.getString(R.string.zen_mode_repeat_callers_list);
348             }
349         }
350 
351         return "";
352     }
353 
getStarredContactsSummary()354     public String getStarredContactsSummary() {
355         List<String> starredContacts = mBackend.getStarredContacts();
356         int numStarredContacts = starredContacts.size();
357         MessageFormat msgFormat = new MessageFormat(
358                 mContext.getString(R.string.zen_mode_starred_contacts_summary_contacts),
359                 Locale.getDefault());
360         Map<String, Object> args = new HashMap<>();
361         args.put("count", numStarredContacts);
362         if (numStarredContacts >= 1) {
363             args.put("contact_1", starredContacts.get(0));
364             if (numStarredContacts >= 2) {
365                 args.put("contact_2", starredContacts.get(1));
366                 if (numStarredContacts == 3) {
367                     args.put("contact_3", starredContacts.get(2));
368                 }
369             }
370         }
371         return msgFormat.format(args);
372     }
373 
getContactsNumberSummary()374     public String getContactsNumberSummary() {
375         MessageFormat msgFormat = new MessageFormat(
376                 mContext.getString(R.string.zen_mode_contacts_count),
377                 Locale.getDefault());
378         Map<String, Object> args = new HashMap<>();
379         args.put("count", mBackend.queryAllContactsData().getCount());
380         return msgFormat.format(args);
381     }
382 
getPeopleSummary(ZenMode zenMode)383     public String getPeopleSummary(ZenMode zenMode) {
384         final int callersAllowed = zenMode.getPolicy().getPriorityCallSenders();
385         final int messagesAllowed = zenMode.getPolicy().getPriorityMessageSenders();
386         final int conversationsAllowed = zenMode.getPolicy().getPriorityConversationSenders();
387         final boolean areRepeatCallersAllowed =
388                 zenMode.getPolicy().isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
389 
390         if (callersAllowed == PEOPLE_TYPE_ANYONE
391                 && messagesAllowed == PEOPLE_TYPE_ANYONE
392                 && conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
393             return mContext.getResources().getString(R.string.zen_mode_people_all);
394         } else if (callersAllowed == PEOPLE_TYPE_NONE
395                 && messagesAllowed == PEOPLE_TYPE_NONE
396                 && conversationsAllowed == CONVERSATION_SENDERS_NONE
397                 && !areRepeatCallersAllowed) {
398             return mContext.getResources().getString(R.string.zen_mode_people_none);
399         } else {
400             return mContext.getResources().getString(R.string.zen_mode_people_some);
401         }
402     }
403 
404     /**
405      * Generates a summary to display under the top level "Apps" preference for a mode, based
406      * on the given mode and provided set of apps.
407      */
getAppsSummary(@onNull ZenMode zenMode, @Nullable Set<String> appsBypassing)408     public @NonNull String getAppsSummary(@NonNull ZenMode zenMode,
409             @Nullable Set<String> appsBypassing) {
410         if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_PRIORITY) {
411             return formatAppsList(appsBypassing);
412         } else if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
413             return mContext.getResources().getString(R.string.zen_mode_apps_none_apps);
414         } else if (zenMode.getPolicy().getAllowedChannels() == ZenMode.CHANNEL_POLICY_ALL) {
415             return mContext.getResources().getString(R.string.zen_mode_apps_all_apps);
416         }
417         return "";
418     }
419 
420     /**
421      * Generates a formatted string declaring which apps can interrupt in the style of
422      * "App, App2, and 4 more can interrupt."
423      * Apps selected for explicit mention are selected in order from the provided set sorted
424      * alphabetically.
425      */
formatAppsList(@ullable Set<String> appsBypassingDnd)426     public @NonNull String formatAppsList(@Nullable Set<String> appsBypassingDnd) {
427         if (appsBypassingDnd == null) {
428             return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps);
429         }
430         final int numAppsBypassingDnd = appsBypassingDnd.size();
431         String[] appsBypassingDndArr = appsBypassingDnd.toArray(new String[numAppsBypassingDnd]);
432         // Sorts the provided apps alphabetically.
433         Arrays.sort(appsBypassingDndArr);
434         MessageFormat msgFormat = new MessageFormat(
435                 mContext.getString(R.string.zen_mode_apps_subtext),
436                 Locale.getDefault());
437         Map<String, Object> args = new HashMap<>();
438         args.put("count", numAppsBypassingDnd);
439         if (numAppsBypassingDnd >= 1) {
440             args.put("app_1", appsBypassingDndArr[0]);
441             if (numAppsBypassingDnd >= 2) {
442                 args.put("app_2", appsBypassingDndArr[1]);
443                 if (numAppsBypassingDnd == 3) {
444                     args.put("app_3", appsBypassingDndArr[2]);
445                 }
446             }
447         }
448         return msgFormat.format(args);
449     }
450 }
451