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