1 /* 2 * Copyright (C) 2017 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 17 package com.android.settings.notification.zen; 18 19 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; 20 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; 21 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; 22 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE; 23 24 import android.app.ActivityManager; 25 import android.app.AutomaticZenRule; 26 import android.app.NotificationManager; 27 import android.content.Context; 28 import android.database.Cursor; 29 import android.icu.text.ListFormatter; 30 import android.net.Uri; 31 import android.provider.ContactsContract; 32 import android.provider.Settings; 33 import android.service.notification.ZenModeConfig; 34 import android.service.notification.ZenPolicy; 35 import android.util.Log; 36 37 import androidx.annotation.VisibleForTesting; 38 39 import com.android.settings.R; 40 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Comparator; 44 import java.util.List; 45 import java.util.Map; 46 47 public class ZenModeBackend { 48 @VisibleForTesting 49 protected static final String ZEN_MODE_FROM_ANYONE = "zen_mode_from_anyone"; 50 @VisibleForTesting 51 protected static final String ZEN_MODE_FROM_CONTACTS = "zen_mode_from_contacts"; 52 @VisibleForTesting 53 protected static final String ZEN_MODE_FROM_STARRED = "zen_mode_from_starred"; 54 @VisibleForTesting 55 protected static final String ZEN_MODE_FROM_NONE = "zen_mode_from_none"; 56 protected static final int SOURCE_NONE = -1; 57 private static List<String> mDefaultRuleIds; 58 59 private static ZenModeBackend sInstance; 60 61 protected int mZenMode; 62 /** gets policy last set by updatePolicy **/ 63 protected NotificationManager.Policy mPolicy; 64 private final NotificationManager mNotificationManager; 65 66 private String TAG = "ZenModeSettingsBackend"; 67 private final Context mContext; 68 getInstance(Context context)69 public static ZenModeBackend getInstance(Context context) { 70 if (sInstance == null) { 71 sInstance = new ZenModeBackend(context); 72 } 73 return sInstance; 74 } 75 ZenModeBackend(Context context)76 public ZenModeBackend(Context context) { 77 mContext = context; 78 mNotificationManager = (NotificationManager) context.getSystemService( 79 Context.NOTIFICATION_SERVICE); 80 updateZenMode(); 81 updatePolicy(); 82 } 83 updatePolicy()84 protected void updatePolicy() { 85 if (mNotificationManager != null) { 86 mPolicy = mNotificationManager.getNotificationPolicy(); 87 } 88 } 89 updateZenMode()90 protected void updateZenMode() { 91 mZenMode = Settings.Global.getInt(mContext.getContentResolver(), 92 Settings.Global.ZEN_MODE, mZenMode); 93 } 94 updateZenRule(String id, AutomaticZenRule rule)95 protected boolean updateZenRule(String id, AutomaticZenRule rule) { 96 return NotificationManager.from(mContext).updateAutomaticZenRule(id, rule); 97 } 98 setZenMode(int zenMode)99 protected void setZenMode(int zenMode) { 100 NotificationManager.from(mContext).setZenMode(zenMode, null, TAG); 101 mZenMode = getZenMode(); 102 } 103 setZenModeForDuration(int minutes)104 protected void setZenModeForDuration(int minutes) { 105 Uri conditionId = ZenModeConfig.toTimeCondition(mContext, minutes, 106 ActivityManager.getCurrentUser(), true).id; 107 mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 108 conditionId, TAG); 109 mZenMode = getZenMode(); 110 } 111 getZenMode()112 protected int getZenMode() { 113 mZenMode = Settings.Global.getInt(mContext.getContentResolver(), 114 Settings.Global.ZEN_MODE, mZenMode); 115 return mZenMode; 116 } 117 isVisualEffectSuppressed(int visualEffect)118 protected boolean isVisualEffectSuppressed(int visualEffect) { 119 return (mPolicy.suppressedVisualEffects & visualEffect) != 0; 120 } 121 isPriorityCategoryEnabled(int categoryType)122 protected boolean isPriorityCategoryEnabled(int categoryType) { 123 return (mPolicy.priorityCategories & categoryType) != 0; 124 } 125 getNewDefaultPriorityCategories(boolean allow, int categoryType)126 protected int getNewDefaultPriorityCategories(boolean allow, int categoryType) { 127 int priorityCategories = mPolicy.priorityCategories; 128 if (allow) { 129 priorityCategories |= categoryType; 130 } else { 131 priorityCategories &= ~categoryType; 132 } 133 return priorityCategories; 134 } 135 getPriorityCallSenders()136 protected int getPriorityCallSenders() { 137 if (isPriorityCategoryEnabled(NotificationManager.Policy.PRIORITY_CATEGORY_CALLS)) { 138 return mPolicy.priorityCallSenders; 139 } 140 141 return SOURCE_NONE; 142 } 143 getPriorityMessageSenders()144 protected int getPriorityMessageSenders() { 145 if (isPriorityCategoryEnabled( 146 NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES)) { 147 return mPolicy.priorityMessageSenders; 148 } 149 return SOURCE_NONE; 150 } 151 getPriorityConversationSenders()152 protected int getPriorityConversationSenders() { 153 if (isPriorityCategoryEnabled(PRIORITY_CATEGORY_CONVERSATIONS)) { 154 return mPolicy.priorityConversationSenders; 155 } 156 return CONVERSATION_SENDERS_NONE; 157 } 158 saveVisualEffectsPolicy(int category, boolean suppress)159 protected void saveVisualEffectsPolicy(int category, boolean suppress) { 160 Settings.Secure.putInt(mContext.getContentResolver(), 161 Settings.Secure.ZEN_SETTINGS_UPDATED, 1); 162 163 int suppressedEffects = getNewSuppressedEffects(suppress, category); 164 savePolicy(mPolicy.priorityCategories, mPolicy.priorityCallSenders, 165 mPolicy.priorityMessageSenders, suppressedEffects, 166 mPolicy.priorityConversationSenders); 167 } 168 saveSoundPolicy(int category, boolean allow)169 protected void saveSoundPolicy(int category, boolean allow) { 170 int priorityCategories = getNewDefaultPriorityCategories(allow, category); 171 savePolicy(priorityCategories, mPolicy.priorityCallSenders, 172 mPolicy.priorityMessageSenders, mPolicy.suppressedVisualEffects, 173 mPolicy.priorityConversationSenders); 174 } 175 savePolicy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders, int suppressedVisualEffects, int priorityConversationSenders)176 protected void savePolicy(int priorityCategories, int priorityCallSenders, 177 int priorityMessageSenders, int suppressedVisualEffects, 178 int priorityConversationSenders) { 179 mPolicy = new NotificationManager.Policy(priorityCategories, priorityCallSenders, 180 priorityMessageSenders, suppressedVisualEffects, priorityConversationSenders); 181 mNotificationManager.setNotificationPolicy(mPolicy); 182 } 183 184 getNewSuppressedEffects(boolean suppress, int effectType)185 private int getNewSuppressedEffects(boolean suppress, int effectType) { 186 int effects = mPolicy.suppressedVisualEffects; 187 188 if (suppress) { 189 effects |= effectType; 190 } else { 191 effects &= ~effectType; 192 } 193 194 return clearDeprecatedEffects(effects); 195 } 196 clearDeprecatedEffects(int effects)197 private int clearDeprecatedEffects(int effects) { 198 return effects & ~(SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF); 199 } 200 isEffectAllowed(int effect)201 protected boolean isEffectAllowed(int effect) { 202 return (mPolicy.suppressedVisualEffects & effect) == 0; 203 } 204 saveSenders(int category, int val)205 protected void saveSenders(int category, int val) { 206 int priorityCallSenders = getPriorityCallSenders(); 207 int priorityMessagesSenders = getPriorityMessageSenders(); 208 int categorySenders = getPrioritySenders(category); 209 210 final boolean allowSenders = val != SOURCE_NONE; 211 final int allowSendersFrom = val == SOURCE_NONE ? categorySenders : val; 212 213 String stringCategory = ""; 214 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) { 215 stringCategory = "Calls"; 216 priorityCallSenders = allowSendersFrom; 217 } 218 219 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { 220 stringCategory = "Messages"; 221 priorityMessagesSenders = allowSendersFrom; 222 } 223 224 savePolicy(getNewDefaultPriorityCategories(allowSenders, category), 225 priorityCallSenders, priorityMessagesSenders, mPolicy.suppressedVisualEffects, 226 mPolicy.priorityConversationSenders); 227 228 if (ZenModeSettingsBase.DEBUG) Log.d(TAG, "onPrefChange allow" + 229 stringCategory + "=" + allowSenders + " allow" + stringCategory + "From=" 230 + ZenModeConfig.sourceToString(allowSendersFrom)); 231 } 232 saveConversationSenders(int val)233 protected void saveConversationSenders(int val) { 234 final boolean allowSenders = val != CONVERSATION_SENDERS_NONE; 235 236 savePolicy(getNewDefaultPriorityCategories(allowSenders, PRIORITY_CATEGORY_CONVERSATIONS), 237 mPolicy.priorityCallSenders, mPolicy.priorityMessageSenders, 238 mPolicy.suppressedVisualEffects, val); 239 240 } 241 getPrioritySenders(int category)242 private int getPrioritySenders(int category) { 243 int categorySenders = -1; 244 245 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) { 246 return getPriorityCallSenders(); 247 } 248 249 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { 250 return getPriorityMessageSenders(); 251 } 252 253 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) { 254 return getPriorityConversationSenders(); 255 } 256 257 return categorySenders; 258 } 259 getKeyFromZenPolicySetting(int contactType)260 protected static String getKeyFromZenPolicySetting(int contactType) { 261 switch (contactType) { 262 case ZenPolicy.PEOPLE_TYPE_ANYONE: 263 return ZEN_MODE_FROM_ANYONE; 264 case ZenPolicy.PEOPLE_TYPE_CONTACTS: 265 return ZEN_MODE_FROM_CONTACTS; 266 case ZenPolicy.PEOPLE_TYPE_STARRED: 267 return ZEN_MODE_FROM_STARRED; 268 case ZenPolicy.PEOPLE_TYPE_NONE: 269 default: 270 return ZEN_MODE_FROM_NONE; 271 } 272 } 273 getKeyFromSetting(int contactType)274 protected static String getKeyFromSetting(int contactType) { 275 switch (contactType) { 276 case NotificationManager.Policy.PRIORITY_SENDERS_ANY: 277 return ZEN_MODE_FROM_ANYONE; 278 case NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS: 279 return ZEN_MODE_FROM_CONTACTS; 280 case NotificationManager.Policy.PRIORITY_SENDERS_STARRED: 281 return ZEN_MODE_FROM_STARRED; 282 case SOURCE_NONE: 283 default: 284 return ZEN_MODE_FROM_NONE; 285 } 286 } 287 getAlarmsTotalSilencePeopleSummary(int category)288 protected int getAlarmsTotalSilencePeopleSummary(int category) { 289 if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { 290 return R.string.zen_mode_none_messages; 291 } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){ 292 return R.string.zen_mode_none_calls; 293 } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) { 294 return R.string.zen_mode_from_no_conversations; 295 } 296 return R.string.zen_mode_from_no_conversations; 297 } 298 getConversationSummary()299 protected int getConversationSummary() { 300 int conversationType = getPriorityConversationSenders(); 301 302 switch (conversationType) { 303 case NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE: 304 return R.string.zen_mode_from_all_conversations; 305 case NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT: 306 return R.string.zen_mode_from_important_conversations; 307 case NotificationManager.Policy.CONVERSATION_SENDERS_NONE: 308 return R.string.zen_mode_from_no_conversations; 309 default: 310 return R.string.zen_mode_from_no_conversations; 311 } 312 } 313 getContactsCallsSummary(ZenPolicy policy)314 protected int getContactsCallsSummary(ZenPolicy policy) { 315 int peopleType = policy.getPriorityCallSenders(); 316 switch (peopleType) { 317 case ZenPolicy.PEOPLE_TYPE_ANYONE: 318 return R.string.zen_mode_from_anyone; 319 case ZenPolicy.PEOPLE_TYPE_CONTACTS: 320 return R.string.zen_mode_from_contacts; 321 case ZenPolicy.PEOPLE_TYPE_STARRED: 322 return R.string.zen_mode_from_starred; 323 case ZenPolicy.PEOPLE_TYPE_NONE: 324 default: 325 return R.string.zen_mode_none_calls; 326 } 327 } 328 getContactsMessagesSummary(ZenPolicy policy)329 protected int getContactsMessagesSummary(ZenPolicy policy) { 330 int peopleType = policy.getPriorityMessageSenders(); 331 switch (peopleType) { 332 case ZenPolicy.PEOPLE_TYPE_ANYONE: 333 return R.string.zen_mode_from_anyone; 334 case ZenPolicy.PEOPLE_TYPE_CONTACTS: 335 return R.string.zen_mode_from_contacts; 336 case ZenPolicy.PEOPLE_TYPE_STARRED: 337 return R.string.zen_mode_from_starred; 338 case ZenPolicy.PEOPLE_TYPE_NONE: 339 default: 340 return R.string.zen_mode_none_messages; 341 } 342 } 343 getZenPolicySettingFromPrefKey(String key)344 protected static int getZenPolicySettingFromPrefKey(String key) { 345 switch (key) { 346 case ZEN_MODE_FROM_ANYONE: 347 return ZenPolicy.PEOPLE_TYPE_ANYONE; 348 case ZEN_MODE_FROM_CONTACTS: 349 return ZenPolicy.PEOPLE_TYPE_CONTACTS; 350 case ZEN_MODE_FROM_STARRED: 351 return ZenPolicy.PEOPLE_TYPE_STARRED; 352 case ZEN_MODE_FROM_NONE: 353 default: 354 return ZenPolicy.PEOPLE_TYPE_NONE; 355 } 356 } 357 removeZenRule(String ruleId)358 public boolean removeZenRule(String ruleId) { 359 return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId); 360 } 361 getConsolidatedPolicy()362 public NotificationManager.Policy getConsolidatedPolicy() { 363 return NotificationManager.from(mContext).getConsolidatedNotificationPolicy(); 364 } 365 addZenRule(AutomaticZenRule rule)366 protected String addZenRule(AutomaticZenRule rule) { 367 try { 368 return NotificationManager.from(mContext).addAutomaticZenRule(rule); 369 } catch (Exception e) { 370 return null; 371 } 372 } 373 setDefaultZenPolicy(ZenPolicy zenPolicy)374 ZenPolicy setDefaultZenPolicy(ZenPolicy zenPolicy) { 375 int calls; 376 if (mPolicy.allowCalls()) { 377 calls = ZenModeConfig.getZenPolicySenders(mPolicy.allowCallsFrom()); 378 } else { 379 calls = ZenPolicy.PEOPLE_TYPE_NONE; 380 } 381 382 int messages; 383 if (mPolicy.allowMessages()) { 384 messages = ZenModeConfig.getZenPolicySenders(mPolicy.allowMessagesFrom()); 385 } else { 386 messages = ZenPolicy.PEOPLE_TYPE_NONE; 387 } 388 389 int conversations; 390 if (mPolicy.allowConversations()) { 391 // unlike the above, no mapping is needed because the values are the same 392 conversations = mPolicy.allowConversationsFrom(); 393 } else { 394 conversations = CONVERSATION_SENDERS_NONE; 395 } 396 397 return new ZenPolicy.Builder(zenPolicy) 398 .allowAlarms(mPolicy.allowAlarms()) 399 .allowCalls(calls) 400 .allowEvents(mPolicy.allowEvents()) 401 .allowMedia(mPolicy.allowMedia()) 402 .allowMessages(messages) 403 .allowConversations(conversations) 404 .allowReminders(mPolicy.allowReminders()) 405 .allowRepeatCallers(mPolicy.allowRepeatCallers()) 406 .allowSystem(mPolicy.allowSystem()) 407 .showFullScreenIntent(mPolicy.showFullScreenIntents()) 408 .showLights(mPolicy.showLights()) 409 .showInAmbientDisplay(mPolicy.showAmbient()) 410 .showInNotificationList(mPolicy.showInNotificationList()) 411 .showBadges(mPolicy.showBadges()) 412 .showPeeking(mPolicy.showPeeking()) 413 .showStatusBarIcons(mPolicy.showStatusBarIcons()) 414 .build(); 415 } 416 getAutomaticZenRules()417 protected Map.Entry<String, AutomaticZenRule>[] getAutomaticZenRules() { 418 Map<String, AutomaticZenRule> ruleMap = 419 NotificationManager.from(mContext).getAutomaticZenRules(); 420 final Map.Entry<String, AutomaticZenRule>[] rt = ruleMap.entrySet().toArray( 421 new Map.Entry[ruleMap.size()]); 422 Arrays.sort(rt, RULE_COMPARATOR); 423 return rt; 424 } 425 getAutomaticZenRule(String id)426 protected AutomaticZenRule getAutomaticZenRule(String id) { 427 return NotificationManager.from(mContext).getAutomaticZenRule(id); 428 } 429 getDefaultRuleIds()430 private static List<String> getDefaultRuleIds() { 431 if (mDefaultRuleIds == null) { 432 mDefaultRuleIds = ZenModeConfig.DEFAULT_RULE_IDS; 433 } 434 return mDefaultRuleIds; 435 } 436 toNotificationPolicy(ZenPolicy policy)437 NotificationManager.Policy toNotificationPolicy(ZenPolicy policy) { 438 ZenModeConfig config = new ZenModeConfig(); 439 return config.toNotificationPolicy(policy); 440 } 441 442 @VisibleForTesting getStarredContacts(Cursor cursor)443 List<String> getStarredContacts(Cursor cursor) { 444 List<String> starredContacts = new ArrayList<>(); 445 if (cursor != null && cursor.moveToFirst()) { 446 do { 447 String contact = cursor.getString(0); 448 if (contact != null) { 449 starredContacts.add(contact); 450 } 451 } while (cursor.moveToNext()); 452 } 453 return starredContacts; 454 } 455 getStarredContacts()456 private List<String> getStarredContacts() { 457 Cursor cursor = null; 458 try { 459 cursor = queryStarredContactsData(); 460 return getStarredContacts(cursor); 461 } finally { 462 if (cursor != null) { 463 cursor.close(); 464 } 465 } 466 } 467 getStarredContactsSummary(Context context)468 String getStarredContactsSummary(Context context) { 469 List<String> starredContacts = getStarredContacts(); 470 int numStarredContacts = starredContacts.size(); 471 472 List<String> displayContacts = new ArrayList<>(); 473 474 if (numStarredContacts == 0) { 475 displayContacts.add(context.getString(R.string.zen_mode_starred_contacts_summary_none)); 476 } else { 477 for (int i = 0; i < 2 && i < numStarredContacts; i++) { 478 displayContacts.add(starredContacts.get(i)); 479 } 480 481 if (numStarredContacts == 3) { 482 displayContacts.add(starredContacts.get(2)); 483 } else if (numStarredContacts > 2) { 484 displayContacts.add(context.getResources().getQuantityString( 485 R.plurals.zen_mode_starred_contacts_summary_additional_contacts, 486 numStarredContacts - 2, numStarredContacts - 2)); 487 } 488 } 489 490 // values in displayContacts must not be null 491 return ListFormatter.getInstance().format(displayContacts); 492 } 493 getContactsNumberSummary(Context context)494 String getContactsNumberSummary(Context context) { 495 final int numContacts = queryAllContactsData().getCount(); 496 if (numContacts == 0) { 497 return context.getResources().getString( 498 R.string.zen_mode_contacts_count_none); 499 } 500 return context.getResources().getQuantityString(R.plurals.zen_mode_contacts_count, 501 numContacts, numContacts); 502 } 503 queryStarredContactsData()504 private Cursor queryStarredContactsData() { 505 return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, 506 new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}, 507 ContactsContract.Data.STARRED + "=1", null, 508 ContactsContract.Data.TIMES_CONTACTED); 509 } 510 queryAllContactsData()511 private Cursor queryAllContactsData() { 512 return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, 513 new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY}, 514 null, null, null); 515 } 516 517 @VisibleForTesting 518 public static final Comparator<Map.Entry<String, AutomaticZenRule>> RULE_COMPARATOR = 519 new Comparator<Map.Entry<String, AutomaticZenRule>>() { 520 @Override 521 public int compare(Map.Entry<String, AutomaticZenRule> lhs, 522 Map.Entry<String, AutomaticZenRule> rhs) { 523 // if it's a default rule, should be at the top of automatic rules 524 boolean lhsIsDefaultRule = getDefaultRuleIds().contains(lhs.getKey()); 525 boolean rhsIsDefaultRule = getDefaultRuleIds().contains(rhs.getKey()); 526 if (lhsIsDefaultRule != rhsIsDefaultRule) { 527 return lhsIsDefaultRule ? -1 : 1; 528 } 529 530 int byDate = Long.compare(lhs.getValue().getCreationTime(), 531 rhs.getValue().getCreationTime()); 532 if (byDate != 0) { 533 return byDate; 534 } else { 535 return key(lhs.getValue()).compareTo(key(rhs.getValue())); 536 } 537 } 538 539 private String key(AutomaticZenRule rule) { 540 final int type = ZenModeConfig.isValidScheduleConditionId(rule.getConditionId()) 541 ? 1 : ZenModeConfig.isValidEventConditionId(rule.getConditionId()) 542 ? 2 : 3; 543 return type + rule.getName().toString(); 544 } 545 }; 546 } 547