1 /** 2 * Copyright (c) 2014, The Android Open Source Project 3 * 4 * Licensed under the Apache License, 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 android.service.notification; 18 19 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; 20 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; 21 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; 22 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; 23 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; 24 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; 25 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; 26 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; 27 28 import android.annotation.Nullable; 29 import android.app.ActivityManager; 30 import android.app.AlarmManager; 31 import android.app.NotificationManager; 32 import android.app.NotificationManager.Policy; 33 import android.compat.annotation.UnsupportedAppUsage; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.PackageManager; 38 import android.content.res.Resources; 39 import android.net.Uri; 40 import android.os.Build; 41 import android.os.Parcel; 42 import android.os.Parcelable; 43 import android.os.UserHandle; 44 import android.provider.Settings.Global; 45 import android.text.TextUtils; 46 import android.text.format.DateFormat; 47 import android.util.ArrayMap; 48 import android.util.PluralsMessageFormatter; 49 import android.util.Slog; 50 import android.util.proto.ProtoOutputStream; 51 52 import com.android.internal.R; 53 import com.android.internal.util.XmlUtils; 54 import com.android.modules.utils.TypedXmlPullParser; 55 import com.android.modules.utils.TypedXmlSerializer; 56 57 import org.xmlpull.v1.XmlPullParser; 58 import org.xmlpull.v1.XmlPullParserException; 59 60 import java.io.IOException; 61 import java.util.Arrays; 62 import java.util.Calendar; 63 import java.util.Date; 64 import java.util.GregorianCalendar; 65 import java.util.HashMap; 66 import java.util.List; 67 import java.util.Locale; 68 import java.util.Map; 69 import java.util.Objects; 70 import java.util.TimeZone; 71 import java.util.UUID; 72 73 /** 74 * Persisted configuration for zen mode. 75 * 76 * @hide 77 */ 78 public class ZenModeConfig implements Parcelable { 79 private static String TAG = "ZenModeConfig"; 80 81 public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY; 82 public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS; 83 public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED; 84 public static final int MAX_SOURCE = SOURCE_STAR; 85 private static final int DEFAULT_SOURCE = SOURCE_STAR; 86 private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR; 87 88 public static final String MANUAL_RULE_ID = "MANUAL_RULE"; 89 public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; 90 public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; 91 public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID, 92 EVENTS_DEFAULT_RULE_ID); 93 94 public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 95 Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; 96 97 public static final int[] MINUTE_BUCKETS = generateMinuteBuckets(); 98 private static final int SECONDS_MS = 1000; 99 private static final int MINUTES_MS = 60 * SECONDS_MS; 100 private static final int DAY_MINUTES = 24 * 60; 101 private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; 102 103 // Default allow categories set in readXml() from default_zen_mode_config.xml, 104 // fallback/upgrade values: 105 private static final boolean DEFAULT_ALLOW_ALARMS = true; 106 private static final boolean DEFAULT_ALLOW_MEDIA = true; 107 private static final boolean DEFAULT_ALLOW_SYSTEM = false; 108 private static final boolean DEFAULT_ALLOW_CALLS = true; 109 private static final boolean DEFAULT_ALLOW_MESSAGES = true; 110 private static final boolean DEFAULT_ALLOW_REMINDERS = false; 111 private static final boolean DEFAULT_ALLOW_EVENTS = false; 112 private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true; 113 private static final boolean DEFAULT_ALLOW_CONV = true; 114 private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; 115 private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false; 116 // Default setting here is 010011101 = 157 117 private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 118 SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT 119 | SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT; 120 121 public static final int XML_VERSION = 8; 122 public static final String ZEN_TAG = "zen"; 123 private static final String ZEN_ATT_VERSION = "version"; 124 private static final String ZEN_ATT_USER = "user"; 125 private static final String ALLOW_TAG = "allow"; 126 private static final String ALLOW_ATT_ALARMS = "alarms"; 127 private static final String ALLOW_ATT_MEDIA = "media"; 128 private static final String ALLOW_ATT_SYSTEM = "system"; 129 private static final String ALLOW_ATT_CALLS = "calls"; 130 private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; 131 private static final String ALLOW_ATT_MESSAGES = "messages"; 132 private static final String ALLOW_ATT_FROM = "from"; 133 private static final String ALLOW_ATT_CALLS_FROM = "callsFrom"; 134 private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom"; 135 private static final String ALLOW_ATT_REMINDERS = "reminders"; 136 private static final String ALLOW_ATT_EVENTS = "events"; 137 private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff"; 138 private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; 139 private static final String ALLOW_ATT_CONV = "convos"; 140 private static final String ALLOW_ATT_CONV_FROM = "convosFrom"; 141 private static final String DISALLOW_TAG = "disallow"; 142 private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; 143 private static final String STATE_TAG = "state"; 144 private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd"; 145 146 // zen policy visual effects attributes 147 private static final String SHOW_ATT_FULL_SCREEN_INTENT = "showFullScreenIntent"; 148 private static final String SHOW_ATT_LIGHTS = "showLights"; 149 private static final String SHOW_ATT_PEEK = "shoePeek"; 150 private static final String SHOW_ATT_STATUS_BAR_ICONS = "showStatusBarIcons"; 151 private static final String SHOW_ATT_BADGES = "showBadges"; 152 private static final String SHOW_ATT_AMBIENT = "showAmbient"; 153 private static final String SHOW_ATT_NOTIFICATION_LIST = "showNotificationList"; 154 155 private static final String CONDITION_ATT_ID = "id"; 156 private static final String CONDITION_ATT_SUMMARY = "summary"; 157 private static final String CONDITION_ATT_LINE1 = "line1"; 158 private static final String CONDITION_ATT_LINE2 = "line2"; 159 private static final String CONDITION_ATT_ICON = "icon"; 160 private static final String CONDITION_ATT_STATE = "state"; 161 private static final String CONDITION_ATT_FLAGS = "flags"; 162 163 private static final String ZEN_POLICY_TAG = "zen_policy"; 164 165 private static final String MANUAL_TAG = "manual"; 166 private static final String AUTOMATIC_TAG = "automatic"; 167 168 private static final String RULE_ATT_ID = "ruleId"; 169 private static final String RULE_ATT_ENABLED = "enabled"; 170 private static final String RULE_ATT_SNOOZING = "snoozing"; 171 private static final String RULE_ATT_NAME = "name"; 172 private static final String RULE_ATT_PKG = "pkg"; 173 private static final String RULE_ATT_COMPONENT = "component"; 174 private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity"; 175 private static final String RULE_ATT_ZEN = "zen"; 176 private static final String RULE_ATT_CONDITION_ID = "conditionId"; 177 private static final String RULE_ATT_CREATION_TIME = "creationTime"; 178 private static final String RULE_ATT_ENABLER = "enabler"; 179 private static final String RULE_ATT_MODIFIED = "modified"; 180 181 @UnsupportedAppUsage 182 public boolean allowAlarms = DEFAULT_ALLOW_ALARMS; 183 public boolean allowMedia = DEFAULT_ALLOW_MEDIA; 184 public boolean allowSystem = DEFAULT_ALLOW_SYSTEM; 185 public boolean allowCalls = DEFAULT_ALLOW_CALLS; 186 public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; 187 public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; 188 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 189 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 190 public int allowCallsFrom = DEFAULT_CALLS_SOURCE; 191 public int allowMessagesFrom = DEFAULT_SOURCE; 192 public boolean allowConversations = DEFAULT_ALLOW_CONV; 193 public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM; 194 public int user = UserHandle.USER_SYSTEM; 195 public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS; 196 public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND; 197 public int version; 198 199 public ZenRule manualRule; 200 @UnsupportedAppUsage 201 public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); 202 203 @UnsupportedAppUsage ZenModeConfig()204 public ZenModeConfig() { } 205 ZenModeConfig(Parcel source)206 public ZenModeConfig(Parcel source) { 207 allowCalls = source.readInt() == 1; 208 allowRepeatCallers = source.readInt() == 1; 209 allowMessages = source.readInt() == 1; 210 allowReminders = source.readInt() == 1; 211 allowEvents = source.readInt() == 1; 212 allowCallsFrom = source.readInt(); 213 allowMessagesFrom = source.readInt(); 214 user = source.readInt(); 215 manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class); 216 final int len = source.readInt(); 217 if (len > 0) { 218 final String[] ids = new String[len]; 219 final ZenRule[] rules = new ZenRule[len]; 220 source.readStringArray(ids); 221 source.readTypedArray(rules, ZenRule.CREATOR); 222 for (int i = 0; i < len; i++) { 223 automaticRules.put(ids[i], rules[i]); 224 } 225 } 226 allowAlarms = source.readInt() == 1; 227 allowMedia = source.readInt() == 1; 228 allowSystem = source.readInt() == 1; 229 suppressedVisualEffects = source.readInt(); 230 areChannelsBypassingDnd = source.readInt() == 1; 231 allowConversations = source.readBoolean(); 232 allowConversationsFrom = source.readInt(); 233 } 234 235 @Override writeToParcel(Parcel dest, int flags)236 public void writeToParcel(Parcel dest, int flags) { 237 dest.writeInt(allowCalls ? 1 : 0); 238 dest.writeInt(allowRepeatCallers ? 1 : 0); 239 dest.writeInt(allowMessages ? 1 : 0); 240 dest.writeInt(allowReminders ? 1 : 0); 241 dest.writeInt(allowEvents ? 1 : 0); 242 dest.writeInt(allowCallsFrom); 243 dest.writeInt(allowMessagesFrom); 244 dest.writeInt(user); 245 dest.writeParcelable(manualRule, 0); 246 if (!automaticRules.isEmpty()) { 247 final int len = automaticRules.size(); 248 final String[] ids = new String[len]; 249 final ZenRule[] rules = new ZenRule[len]; 250 for (int i = 0; i < len; i++) { 251 ids[i] = automaticRules.keyAt(i); 252 rules[i] = automaticRules.valueAt(i); 253 } 254 dest.writeInt(len); 255 dest.writeStringArray(ids); 256 dest.writeTypedArray(rules, 0); 257 } else { 258 dest.writeInt(0); 259 } 260 dest.writeInt(allowAlarms ? 1 : 0); 261 dest.writeInt(allowMedia ? 1 : 0); 262 dest.writeInt(allowSystem ? 1 : 0); 263 dest.writeInt(suppressedVisualEffects); 264 dest.writeInt(areChannelsBypassingDnd ? 1 : 0); 265 dest.writeBoolean(allowConversations); 266 dest.writeInt(allowConversationsFrom); 267 } 268 269 @Override toString()270 public String toString() { 271 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') 272 .append("user=").append(user) 273 .append(",allowAlarms=").append(allowAlarms) 274 .append(",allowMedia=").append(allowMedia) 275 .append(",allowSystem=").append(allowSystem) 276 .append(",allowReminders=").append(allowReminders) 277 .append(",allowEvents=").append(allowEvents) 278 .append(",allowCalls=").append(allowCalls) 279 .append(",allowRepeatCallers=").append(allowRepeatCallers) 280 .append(",allowMessages=").append(allowMessages) 281 .append(",allowConversations=").append(allowConversations) 282 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) 283 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) 284 .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString 285 (allowConversationsFrom)) 286 .append(",suppressedVisualEffects=").append(suppressedVisualEffects) 287 .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd) 288 .append(",\nautomaticRules=").append(rulesToString()) 289 .append(",\nmanualRule=").append(manualRule) 290 .append(']').toString(); 291 } 292 rulesToString()293 private String rulesToString() { 294 if (automaticRules.isEmpty()) { 295 return "{}"; 296 } 297 298 StringBuilder buffer = new StringBuilder(automaticRules.size() * 28); 299 buffer.append("{\n"); 300 for (int i = 0; i < automaticRules.size(); i++) { 301 if (i > 0) { 302 buffer.append(",\n"); 303 } 304 Object value = automaticRules.valueAt(i); 305 buffer.append(value); 306 } 307 buffer.append('}'); 308 return buffer.toString(); 309 } 310 isValid()311 public boolean isValid() { 312 if (!isValidManualRule(manualRule)) return false; 313 final int N = automaticRules.size(); 314 for (int i = 0; i < N; i++) { 315 if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false; 316 } 317 return true; 318 } 319 isValidManualRule(ZenRule rule)320 private static boolean isValidManualRule(ZenRule rule) { 321 return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule); 322 } 323 isValidAutomaticRule(ZenRule rule)324 private static boolean isValidAutomaticRule(ZenRule rule) { 325 return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode) 326 && rule.conditionId != null && sameCondition(rule); 327 } 328 sameCondition(ZenRule rule)329 private static boolean sameCondition(ZenRule rule) { 330 if (rule == null) return false; 331 if (rule.conditionId == null) { 332 return rule.condition == null; 333 } else { 334 return rule.condition == null || rule.conditionId.equals(rule.condition.id); 335 } 336 } 337 generateMinuteBuckets()338 private static int[] generateMinuteBuckets() { 339 final int maxHrs = 12; 340 final int[] buckets = new int[maxHrs + 3]; 341 buckets[0] = 15; 342 buckets[1] = 30; 343 buckets[2] = 45; 344 for (int i = 1; i <= maxHrs; i++) { 345 buckets[2 + i] = 60 * i; 346 } 347 return buckets; 348 } 349 sourceToString(int source)350 public static String sourceToString(int source) { 351 switch (source) { 352 case SOURCE_ANYONE: 353 return "anyone"; 354 case SOURCE_CONTACT: 355 return "contacts"; 356 case SOURCE_STAR: 357 return "stars"; 358 default: 359 return "UNKNOWN"; 360 } 361 } 362 363 @Override equals(@ullable Object o)364 public boolean equals(@Nullable Object o) { 365 if (!(o instanceof ZenModeConfig)) return false; 366 if (o == this) return true; 367 final ZenModeConfig other = (ZenModeConfig) o; 368 return other.allowAlarms == allowAlarms 369 && other.allowMedia == allowMedia 370 && other.allowSystem == allowSystem 371 && other.allowCalls == allowCalls 372 && other.allowRepeatCallers == allowRepeatCallers 373 && other.allowMessages == allowMessages 374 && other.allowCallsFrom == allowCallsFrom 375 && other.allowMessagesFrom == allowMessagesFrom 376 && other.allowReminders == allowReminders 377 && other.allowEvents == allowEvents 378 && other.user == user 379 && Objects.equals(other.automaticRules, automaticRules) 380 && Objects.equals(other.manualRule, manualRule) 381 && other.suppressedVisualEffects == suppressedVisualEffects 382 && other.areChannelsBypassingDnd == areChannelsBypassingDnd 383 && other.allowConversations == allowConversations 384 && other.allowConversationsFrom == allowConversationsFrom; 385 } 386 387 @Override hashCode()388 public int hashCode() { 389 return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls, 390 allowRepeatCallers, allowMessages, 391 allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, 392 user, automaticRules, manualRule, 393 suppressedVisualEffects, areChannelsBypassingDnd, allowConversations, 394 allowConversationsFrom); 395 } 396 toDayList(int[] days)397 private static String toDayList(int[] days) { 398 if (days == null || days.length == 0) return ""; 399 final StringBuilder sb = new StringBuilder(); 400 for (int i = 0; i < days.length; i++) { 401 if (i > 0) sb.append('.'); 402 sb.append(days[i]); 403 } 404 return sb.toString(); 405 } 406 tryParseDayList(String dayList, String sep)407 private static int[] tryParseDayList(String dayList, String sep) { 408 if (dayList == null) return null; 409 final String[] tokens = dayList.split(sep); 410 if (tokens.length == 0) return null; 411 final int[] rt = new int[tokens.length]; 412 for (int i = 0; i < tokens.length; i++) { 413 final int day = tryParseInt(tokens[i], -1); 414 if (day == -1) return null; 415 rt[i] = day; 416 } 417 return rt; 418 } 419 tryParseInt(String value, int defValue)420 private static int tryParseInt(String value, int defValue) { 421 if (TextUtils.isEmpty(value)) return defValue; 422 try { 423 return Integer.parseInt(value); 424 } catch (NumberFormatException e) { 425 return defValue; 426 } 427 } 428 tryParseLong(String value, long defValue)429 private static long tryParseLong(String value, long defValue) { 430 if (TextUtils.isEmpty(value)) return defValue; 431 try { 432 return Long.parseLong(value); 433 } catch (NumberFormatException e) { 434 return defValue; 435 } 436 } 437 tryParseLong(String value, Long defValue)438 private static Long tryParseLong(String value, Long defValue) { 439 if (TextUtils.isEmpty(value)) return defValue; 440 try { 441 return Long.parseLong(value); 442 } catch (NumberFormatException e) { 443 return defValue; 444 } 445 } 446 readXml(TypedXmlPullParser parser)447 public static ZenModeConfig readXml(TypedXmlPullParser parser) 448 throws XmlPullParserException, IOException { 449 int type = parser.getEventType(); 450 if (type != XmlPullParser.START_TAG) return null; 451 String tag = parser.getName(); 452 if (!ZEN_TAG.equals(tag)) return null; 453 final ZenModeConfig rt = new ZenModeConfig(); 454 rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION); 455 rt.user = safeInt(parser, ZEN_ATT_USER, rt.user); 456 boolean readSuppressedEffects = false; 457 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 458 tag = parser.getName(); 459 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 460 return rt; 461 } 462 if (type == XmlPullParser.START_TAG) { 463 if (ALLOW_TAG.equals(tag)) { 464 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, 465 DEFAULT_ALLOW_CALLS); 466 rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS, 467 DEFAULT_ALLOW_REPEAT_CALLERS); 468 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, 469 DEFAULT_ALLOW_MESSAGES); 470 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 471 DEFAULT_ALLOW_REMINDERS); 472 rt.allowConversations = safeBoolean(parser, ALLOW_ATT_CONV, DEFAULT_ALLOW_CONV); 473 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); 474 final int from = safeInt(parser, ALLOW_ATT_FROM, -1); 475 final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); 476 final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); 477 rt.allowConversationsFrom = safeInt(parser, ALLOW_ATT_CONV_FROM, 478 DEFAULT_ALLOW_CONV_FROM); 479 if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { 480 rt.allowCallsFrom = callsFrom; 481 rt.allowMessagesFrom = messagesFrom; 482 } else if (isValidSource(from)) { 483 Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from)); 484 rt.allowCallsFrom = from; 485 rt.allowMessagesFrom = from; 486 } else { 487 rt.allowCallsFrom = DEFAULT_CALLS_SOURCE; 488 rt.allowMessagesFrom = DEFAULT_SOURCE; 489 } 490 rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS); 491 rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA, 492 DEFAULT_ALLOW_MEDIA); 493 rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM); 494 495 // migrate old suppressed visual effects fields, if they still exist in the xml 496 Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF); 497 Boolean allowWhenScreenOn = unsafeBoolean(parser, ALLOW_ATT_SCREEN_ON); 498 if (allowWhenScreenOff != null || allowWhenScreenOn != null) { 499 // If either setting exists, then reset the suppressed visual effects field 500 // to 0 (all allowed) so that only the relevant bits are disallowed by 501 // the migrated settings. 502 readSuppressedEffects = true; 503 rt.suppressedVisualEffects = 0; 504 } 505 if (allowWhenScreenOff != null) { 506 if (!allowWhenScreenOff) { 507 rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS 508 | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT 509 | SUPPRESSED_EFFECT_AMBIENT; 510 } 511 } 512 if (allowWhenScreenOn != null) { 513 if (!allowWhenScreenOn) { 514 rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_PEEK; 515 } 516 } 517 if (readSuppressedEffects) { 518 Slog.d(TAG, "Migrated visual effects to " + rt.suppressedVisualEffects); 519 } 520 } else if (DISALLOW_TAG.equals(tag) && !readSuppressedEffects) { 521 // only read from suppressed visual effects field if we haven't just migrated 522 // the values from allowOn/allowOff, lest we wipe out those settings 523 rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS, 524 DEFAULT_SUPPRESSED_VISUAL_EFFECTS); 525 } else if (MANUAL_TAG.equals(tag)) { 526 rt.manualRule = readRuleXml(parser); 527 } else if (AUTOMATIC_TAG.equals(tag)) { 528 final String id = parser.getAttributeValue(null, RULE_ATT_ID); 529 final ZenRule automaticRule = readRuleXml(parser); 530 if (id != null && automaticRule != null) { 531 automaticRule.id = id; 532 rt.automaticRules.put(id, automaticRule); 533 } 534 } else if (STATE_TAG.equals(tag)) { 535 rt.areChannelsBypassingDnd = safeBoolean(parser, 536 STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); 537 } 538 } 539 } 540 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 541 } 542 543 /** 544 * Writes XML of current ZenModeConfig 545 * @param out serializer 546 * @param version uses XML_VERSION if version is null 547 * @throws IOException 548 */ writeXml(TypedXmlSerializer out, Integer version)549 public void writeXml(TypedXmlSerializer out, Integer version) throws IOException { 550 out.startTag(null, ZEN_TAG); 551 out.attribute(null, ZEN_ATT_VERSION, version == null 552 ? Integer.toString(XML_VERSION) : Integer.toString(version)); 553 out.attributeInt(null, ZEN_ATT_USER, user); 554 out.startTag(null, ALLOW_TAG); 555 out.attributeBoolean(null, ALLOW_ATT_CALLS, allowCalls); 556 out.attributeBoolean(null, ALLOW_ATT_REPEAT_CALLERS, allowRepeatCallers); 557 out.attributeBoolean(null, ALLOW_ATT_MESSAGES, allowMessages); 558 out.attributeBoolean(null, ALLOW_ATT_REMINDERS, allowReminders); 559 out.attributeBoolean(null, ALLOW_ATT_EVENTS, allowEvents); 560 out.attributeInt(null, ALLOW_ATT_CALLS_FROM, allowCallsFrom); 561 out.attributeInt(null, ALLOW_ATT_MESSAGES_FROM, allowMessagesFrom); 562 out.attributeBoolean(null, ALLOW_ATT_ALARMS, allowAlarms); 563 out.attributeBoolean(null, ALLOW_ATT_MEDIA, allowMedia); 564 out.attributeBoolean(null, ALLOW_ATT_SYSTEM, allowSystem); 565 out.attributeBoolean(null, ALLOW_ATT_CONV, allowConversations); 566 out.attributeInt(null, ALLOW_ATT_CONV_FROM, allowConversationsFrom); 567 out.endTag(null, ALLOW_TAG); 568 569 out.startTag(null, DISALLOW_TAG); 570 out.attributeInt(null, DISALLOW_ATT_VISUAL_EFFECTS, suppressedVisualEffects); 571 out.endTag(null, DISALLOW_TAG); 572 573 if (manualRule != null) { 574 out.startTag(null, MANUAL_TAG); 575 writeRuleXml(manualRule, out); 576 out.endTag(null, MANUAL_TAG); 577 } 578 final int N = automaticRules.size(); 579 for (int i = 0; i < N; i++) { 580 final String id = automaticRules.keyAt(i); 581 final ZenRule automaticRule = automaticRules.valueAt(i); 582 out.startTag(null, AUTOMATIC_TAG); 583 out.attribute(null, RULE_ATT_ID, id); 584 writeRuleXml(automaticRule, out); 585 out.endTag(null, AUTOMATIC_TAG); 586 } 587 588 out.startTag(null, STATE_TAG); 589 out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd); 590 out.endTag(null, STATE_TAG); 591 592 out.endTag(null, ZEN_TAG); 593 } 594 readRuleXml(TypedXmlPullParser parser)595 public static ZenRule readRuleXml(TypedXmlPullParser parser) { 596 final ZenRule rt = new ZenRule(); 597 rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true); 598 rt.name = parser.getAttributeValue(null, RULE_ATT_NAME); 599 final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN); 600 rt.zenMode = tryParseZenMode(zen, -1); 601 if (rt.zenMode == -1) { 602 Slog.w(TAG, "Bad zen mode in rule xml:" + zen); 603 return null; 604 } 605 rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); 606 rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); 607 rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY); 608 rt.pkg = XmlUtils.readStringAttribute(parser, RULE_ATT_PKG); 609 if (rt.pkg == null) { 610 // backfill from component, if present. configActivity is not safe to backfill from 611 rt.pkg = rt.component != null ? rt.component.getPackageName() : null; 612 } 613 rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0); 614 rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER); 615 rt.condition = readConditionXml(parser); 616 617 // all default rules and user created rules updated to zenMode important interruptions 618 if (rt.zenMode != Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 619 && Condition.isValidId(rt.conditionId, SYSTEM_AUTHORITY)) { 620 Slog.i(TAG, "Updating zenMode of automatic rule " + rt.name); 621 rt.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 622 } 623 rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false); 624 rt.zenPolicy = readZenPolicyXml(parser); 625 return rt; 626 } 627 writeRuleXml(ZenRule rule, TypedXmlSerializer out)628 public static void writeRuleXml(ZenRule rule, TypedXmlSerializer out) throws IOException { 629 out.attributeBoolean(null, RULE_ATT_ENABLED, rule.enabled); 630 if (rule.name != null) { 631 out.attribute(null, RULE_ATT_NAME, rule.name); 632 } 633 out.attributeInt(null, RULE_ATT_ZEN, rule.zenMode); 634 if (rule.pkg != null) { 635 out.attribute(null, RULE_ATT_PKG, rule.pkg); 636 } 637 if (rule.component != null) { 638 out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); 639 } 640 if (rule.configurationActivity != null) { 641 out.attribute(null, RULE_ATT_CONFIG_ACTIVITY, 642 rule.configurationActivity.flattenToString()); 643 } 644 if (rule.conditionId != null) { 645 out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString()); 646 } 647 out.attributeLong(null, RULE_ATT_CREATION_TIME, rule.creationTime); 648 if (rule.enabler != null) { 649 out.attribute(null, RULE_ATT_ENABLER, rule.enabler); 650 } 651 if (rule.condition != null) { 652 writeConditionXml(rule.condition, out); 653 } 654 if (rule.zenPolicy != null) { 655 writeZenPolicyXml(rule.zenPolicy, out); 656 } 657 out.attributeBoolean(null, RULE_ATT_MODIFIED, rule.modified); 658 } 659 readConditionXml(TypedXmlPullParser parser)660 public static Condition readConditionXml(TypedXmlPullParser parser) { 661 final Uri id = safeUri(parser, CONDITION_ATT_ID); 662 if (id == null) return null; 663 final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); 664 final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); 665 final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); 666 final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); 667 final int state = safeInt(parser, CONDITION_ATT_STATE, -1); 668 final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); 669 try { 670 return new Condition(id, summary, line1, line2, icon, state, flags); 671 } catch (IllegalArgumentException e) { 672 Slog.w(TAG, "Unable to read condition xml", e); 673 return null; 674 } 675 } 676 writeConditionXml(Condition c, TypedXmlSerializer out)677 public static void writeConditionXml(Condition c, TypedXmlSerializer out) throws IOException { 678 out.attribute(null, CONDITION_ATT_ID, c.id.toString()); 679 out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); 680 out.attribute(null, CONDITION_ATT_LINE1, c.line1); 681 out.attribute(null, CONDITION_ATT_LINE2, c.line2); 682 out.attributeInt(null, CONDITION_ATT_ICON, c.icon); 683 out.attributeInt(null, CONDITION_ATT_STATE, c.state); 684 out.attributeInt(null, CONDITION_ATT_FLAGS, c.flags); 685 } 686 687 /** 688 * Read the zen policy from xml 689 * Returns null if no zen policy exists 690 */ readZenPolicyXml(TypedXmlPullParser parser)691 public static ZenPolicy readZenPolicyXml(TypedXmlPullParser parser) { 692 boolean policySet = false; 693 694 ZenPolicy.Builder builder = new ZenPolicy.Builder(); 695 final int calls = safeInt(parser, ALLOW_ATT_CALLS_FROM, ZenPolicy.PEOPLE_TYPE_UNSET); 696 final int messages = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, ZenPolicy.PEOPLE_TYPE_UNSET); 697 final int repeatCallers = safeInt(parser, ALLOW_ATT_REPEAT_CALLERS, ZenPolicy.STATE_UNSET); 698 final int conversations = safeInt(parser, ALLOW_ATT_CONV_FROM, 699 ZenPolicy.CONVERSATION_SENDERS_UNSET); 700 final int alarms = safeInt(parser, ALLOW_ATT_ALARMS, ZenPolicy.STATE_UNSET); 701 final int media = safeInt(parser, ALLOW_ATT_MEDIA, ZenPolicy.STATE_UNSET); 702 final int system = safeInt(parser, ALLOW_ATT_SYSTEM, ZenPolicy.STATE_UNSET); 703 final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET); 704 final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET); 705 706 if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) { 707 builder.allowCalls(calls); 708 policySet = true; 709 } 710 if (messages != ZenPolicy.PEOPLE_TYPE_UNSET) { 711 builder.allowMessages(messages); 712 policySet = true; 713 } 714 if (repeatCallers != ZenPolicy.STATE_UNSET) { 715 builder.allowRepeatCallers(repeatCallers == ZenPolicy.STATE_ALLOW); 716 policySet = true; 717 } 718 if (conversations != ZenPolicy.CONVERSATION_SENDERS_UNSET) { 719 builder.allowConversations(conversations); 720 policySet = true; 721 } 722 if (alarms != ZenPolicy.STATE_UNSET) { 723 builder.allowAlarms(alarms == ZenPolicy.STATE_ALLOW); 724 policySet = true; 725 } 726 if (media != ZenPolicy.STATE_UNSET) { 727 builder.allowMedia(media == ZenPolicy.STATE_ALLOW); 728 policySet = true; 729 } 730 if (system != ZenPolicy.STATE_UNSET) { 731 builder.allowSystem(system == ZenPolicy.STATE_ALLOW); 732 policySet = true; 733 } 734 if (events != ZenPolicy.STATE_UNSET) { 735 builder.allowEvents(events == ZenPolicy.STATE_ALLOW); 736 policySet = true; 737 } 738 if (reminders != ZenPolicy.STATE_UNSET) { 739 builder.allowReminders(reminders == ZenPolicy.STATE_ALLOW); 740 policySet = true; 741 } 742 743 final int fullScreenIntent = safeInt(parser, SHOW_ATT_FULL_SCREEN_INTENT, 744 ZenPolicy.STATE_UNSET); 745 final int lights = safeInt(parser, SHOW_ATT_LIGHTS, ZenPolicy.STATE_UNSET); 746 final int peek = safeInt(parser, SHOW_ATT_PEEK, ZenPolicy.STATE_UNSET); 747 final int statusBar = safeInt(parser, SHOW_ATT_STATUS_BAR_ICONS, ZenPolicy.STATE_UNSET); 748 final int badges = safeInt(parser, SHOW_ATT_BADGES, ZenPolicy.STATE_UNSET); 749 final int ambient = safeInt(parser, SHOW_ATT_AMBIENT, ZenPolicy.STATE_UNSET); 750 final int notificationList = safeInt(parser, SHOW_ATT_NOTIFICATION_LIST, 751 ZenPolicy.STATE_UNSET); 752 753 if (fullScreenIntent != ZenPolicy.STATE_UNSET) { 754 builder.showFullScreenIntent(fullScreenIntent == ZenPolicy.STATE_ALLOW); 755 policySet = true; 756 } 757 if (lights != ZenPolicy.STATE_UNSET) { 758 builder.showLights(lights == ZenPolicy.STATE_ALLOW); 759 policySet = true; 760 } 761 if (peek != ZenPolicy.STATE_UNSET) { 762 builder.showPeeking(peek == ZenPolicy.STATE_ALLOW); 763 policySet = true; 764 } 765 if (statusBar != ZenPolicy.STATE_UNSET) { 766 builder.showStatusBarIcons(statusBar == ZenPolicy.STATE_ALLOW); 767 policySet = true; 768 } 769 if (badges != ZenPolicy.STATE_UNSET) { 770 builder.showBadges(badges == ZenPolicy.STATE_ALLOW); 771 policySet = true; 772 } 773 if (ambient != ZenPolicy.STATE_UNSET) { 774 builder.showInAmbientDisplay(ambient == ZenPolicy.STATE_ALLOW); 775 policySet = true; 776 } 777 if (notificationList != ZenPolicy.STATE_UNSET) { 778 builder.showInNotificationList(notificationList == ZenPolicy.STATE_ALLOW); 779 policySet = true; 780 } 781 782 if (policySet) { 783 return builder.build(); 784 } 785 return null; 786 } 787 788 /** 789 * Writes zen policy to xml 790 */ writeZenPolicyXml(ZenPolicy policy, TypedXmlSerializer out)791 public static void writeZenPolicyXml(ZenPolicy policy, TypedXmlSerializer out) 792 throws IOException { 793 writeZenPolicyState(ALLOW_ATT_CALLS_FROM, policy.getPriorityCallSenders(), out); 794 writeZenPolicyState(ALLOW_ATT_MESSAGES_FROM, policy.getPriorityMessageSenders(), out); 795 writeZenPolicyState(ALLOW_ATT_REPEAT_CALLERS, policy.getPriorityCategoryRepeatCallers(), 796 out); 797 writeZenPolicyState(ALLOW_ATT_CONV_FROM, policy.getPriorityConversationSenders(), out); 798 writeZenPolicyState(ALLOW_ATT_ALARMS, policy.getPriorityCategoryAlarms(), out); 799 writeZenPolicyState(ALLOW_ATT_MEDIA, policy.getPriorityCategoryMedia(), out); 800 writeZenPolicyState(ALLOW_ATT_SYSTEM, policy.getPriorityCategorySystem(), out); 801 writeZenPolicyState(ALLOW_ATT_REMINDERS, policy.getPriorityCategoryReminders(), out); 802 writeZenPolicyState(ALLOW_ATT_EVENTS, policy.getPriorityCategoryEvents(), out); 803 804 writeZenPolicyState(SHOW_ATT_FULL_SCREEN_INTENT, policy.getVisualEffectFullScreenIntent(), 805 out); 806 writeZenPolicyState(SHOW_ATT_LIGHTS, policy.getVisualEffectLights(), out); 807 writeZenPolicyState(SHOW_ATT_PEEK, policy.getVisualEffectPeek(), out); 808 writeZenPolicyState(SHOW_ATT_STATUS_BAR_ICONS, policy.getVisualEffectStatusBar(), out); 809 writeZenPolicyState(SHOW_ATT_BADGES, policy.getVisualEffectBadge(), out); 810 writeZenPolicyState(SHOW_ATT_AMBIENT, policy.getVisualEffectAmbient(), out); 811 writeZenPolicyState(SHOW_ATT_NOTIFICATION_LIST, policy.getVisualEffectNotificationList(), 812 out); 813 } 814 writeZenPolicyState(String attr, int val, TypedXmlSerializer out)815 private static void writeZenPolicyState(String attr, int val, TypedXmlSerializer out) 816 throws IOException { 817 if (Objects.equals(attr, ALLOW_ATT_CALLS_FROM) 818 || Objects.equals(attr, ALLOW_ATT_MESSAGES_FROM)) { 819 if (val != ZenPolicy.PEOPLE_TYPE_UNSET) { 820 out.attributeInt(null, attr, val); 821 } 822 } else if (Objects.equals(attr, ALLOW_ATT_CONV_FROM)) { 823 if (val != ZenPolicy.CONVERSATION_SENDERS_UNSET) { 824 out.attributeInt(null, attr, val); 825 } 826 } else { 827 if (val != ZenPolicy.STATE_UNSET) { 828 out.attributeInt(null, attr, val); 829 } 830 } 831 } 832 isValidHour(int val)833 public static boolean isValidHour(int val) { 834 return val >= 0 && val < 24; 835 } 836 isValidMinute(int val)837 public static boolean isValidMinute(int val) { 838 return val >= 0 && val < 60; 839 } 840 isValidSource(int source)841 private static boolean isValidSource(int source) { 842 return source >= SOURCE_ANYONE && source <= MAX_SOURCE; 843 } 844 unsafeBoolean(TypedXmlPullParser parser, String att)845 private static Boolean unsafeBoolean(TypedXmlPullParser parser, String att) { 846 try { 847 return parser.getAttributeBoolean(null, att); 848 } catch (Exception e) { 849 return null; 850 } 851 } 852 safeBoolean(TypedXmlPullParser parser, String att, boolean defValue)853 private static boolean safeBoolean(TypedXmlPullParser parser, String att, boolean defValue) { 854 return parser.getAttributeBoolean(null, att, defValue); 855 } 856 safeBoolean(String val, boolean defValue)857 private static boolean safeBoolean(String val, boolean defValue) { 858 if (TextUtils.isEmpty(val)) return defValue; 859 return Boolean.parseBoolean(val); 860 } 861 safeInt(TypedXmlPullParser parser, String att, int defValue)862 private static int safeInt(TypedXmlPullParser parser, String att, int defValue) { 863 return parser.getAttributeInt(null, att, defValue); 864 } 865 safeComponentName(TypedXmlPullParser parser, String att)866 private static ComponentName safeComponentName(TypedXmlPullParser parser, String att) { 867 final String val = parser.getAttributeValue(null, att); 868 if (TextUtils.isEmpty(val)) return null; 869 return ComponentName.unflattenFromString(val); 870 } 871 safeUri(TypedXmlPullParser parser, String att)872 private static Uri safeUri(TypedXmlPullParser parser, String att) { 873 final String val = parser.getAttributeValue(null, att); 874 if (val == null) return null; 875 return Uri.parse(val); 876 } 877 safeLong(TypedXmlPullParser parser, String att, long defValue)878 private static long safeLong(TypedXmlPullParser parser, String att, long defValue) { 879 final String val = parser.getAttributeValue(null, att); 880 return tryParseLong(val, defValue); 881 } 882 883 @Override describeContents()884 public int describeContents() { 885 return 0; 886 } 887 copy()888 public ZenModeConfig copy() { 889 final Parcel parcel = Parcel.obtain(); 890 try { 891 writeToParcel(parcel, 0); 892 parcel.setDataPosition(0); 893 return new ZenModeConfig(parcel); 894 } finally { 895 parcel.recycle(); 896 } 897 } 898 899 public static final @android.annotation.NonNull Parcelable.Creator<ZenModeConfig> CREATOR 900 = new Parcelable.Creator<ZenModeConfig>() { 901 @Override 902 public ZenModeConfig createFromParcel(Parcel source) { 903 return new ZenModeConfig(source); 904 } 905 906 @Override 907 public ZenModeConfig[] newArray(int size) { 908 return new ZenModeConfig[size]; 909 } 910 }; 911 912 /** 913 * Converts a ZenModeConfig to a ZenPolicy 914 */ toZenPolicy()915 public ZenPolicy toZenPolicy() { 916 ZenPolicy.Builder builder = new ZenPolicy.Builder() 917 .allowCalls(allowCalls 918 ? ZenModeConfig.getZenPolicySenders(allowCallsFrom) 919 : ZenPolicy.PEOPLE_TYPE_NONE) 920 .allowRepeatCallers(allowRepeatCallers) 921 .allowMessages(allowMessages 922 ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom) 923 : ZenPolicy.PEOPLE_TYPE_NONE) 924 .allowReminders(allowReminders) 925 .allowEvents(allowEvents) 926 .allowAlarms(allowAlarms) 927 .allowMedia(allowMedia) 928 .allowSystem(allowSystem) 929 .allowConversations(allowConversations ? allowConversationsFrom 930 : ZenPolicy.CONVERSATION_SENDERS_NONE); 931 if (suppressedVisualEffects == 0) { 932 builder.showAllVisualEffects(); 933 } else { 934 // configs don't have an unset state: wither true or false. 935 builder.showFullScreenIntent( 936 (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0); 937 builder.showLights( 938 (suppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) == 0); 939 builder.showPeeking( 940 (suppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) == 0); 941 builder.showStatusBarIcons( 942 (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_STATUS_BAR) == 0); 943 builder.showBadges( 944 (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_BADGE) == 0); 945 builder.showInAmbientDisplay( 946 (suppressedVisualEffects & SUPPRESSED_EFFECT_AMBIENT) == 0); 947 builder.showInNotificationList( 948 (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0); 949 } 950 return builder.build(); 951 } 952 953 /** 954 * Converts a zenPolicy to a notificationPolicy using this ZenModeConfig's values as its 955 * defaults for all unset values in zenPolicy 956 */ toNotificationPolicy(ZenPolicy zenPolicy)957 public Policy toNotificationPolicy(ZenPolicy zenPolicy) { 958 NotificationManager.Policy defaultPolicy = toNotificationPolicy(); 959 int priorityCategories = 0; 960 int suppressedVisualEffects = 0; 961 int callSenders = defaultPolicy.priorityCallSenders; 962 int messageSenders = defaultPolicy.priorityMessageSenders; 963 int conversationSenders = defaultPolicy.priorityConversationSenders; 964 965 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REMINDERS, 966 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REMINDERS, defaultPolicy))) { 967 priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; 968 } 969 970 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_EVENTS, 971 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_EVENTS, defaultPolicy))) { 972 priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; 973 } 974 975 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES, 976 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) { 977 priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; 978 messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(), 979 messageSenders); 980 } 981 982 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS, 983 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) { 984 priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; 985 conversationSenders = getConversationSendersWithDefault( 986 zenPolicy.getPriorityConversationSenders(), conversationSenders); 987 } 988 989 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS, 990 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) { 991 priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; 992 callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(), 993 callSenders); 994 } 995 996 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS, 997 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 998 defaultPolicy))) { 999 priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; 1000 } 1001 1002 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_ALARMS, 1003 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_ALARMS, defaultPolicy))) { 1004 priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS; 1005 } 1006 1007 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MEDIA, 1008 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MEDIA, defaultPolicy))) { 1009 priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA; 1010 } 1011 1012 if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_SYSTEM, 1013 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_SYSTEM, defaultPolicy))) { 1014 priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM; 1015 } 1016 1017 boolean suppressFullScreenIntent = !zenPolicy.isVisualEffectAllowed( 1018 ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT, 1019 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT, 1020 defaultPolicy)); 1021 1022 boolean suppressLights = !zenPolicy.isVisualEffectAllowed( 1023 ZenPolicy.VISUAL_EFFECT_LIGHTS, 1024 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_LIGHTS, 1025 defaultPolicy)); 1026 1027 boolean suppressAmbient = !zenPolicy.isVisualEffectAllowed( 1028 ZenPolicy.VISUAL_EFFECT_AMBIENT, 1029 isVisualEffectAllowed(SUPPRESSED_EFFECT_AMBIENT, 1030 defaultPolicy)); 1031 1032 if (suppressFullScreenIntent && suppressLights && suppressAmbient) { 1033 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_OFF; 1034 } 1035 1036 if (suppressFullScreenIntent) { 1037 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; 1038 } 1039 1040 if (suppressLights) { 1041 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; 1042 } 1043 1044 if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_PEEK, 1045 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_PEEK, 1046 defaultPolicy))) { 1047 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK; 1048 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON; 1049 } 1050 1051 if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_STATUS_BAR, 1052 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_STATUS_BAR, 1053 defaultPolicy))) { 1054 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_STATUS_BAR; 1055 } 1056 1057 if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_BADGE, 1058 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_BADGE, 1059 defaultPolicy))) { 1060 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; 1061 } 1062 1063 if (suppressAmbient) { 1064 suppressedVisualEffects |= SUPPRESSED_EFFECT_AMBIENT; 1065 } 1066 1067 if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST, 1068 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST, 1069 defaultPolicy))) { 1070 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; 1071 } 1072 1073 return new NotificationManager.Policy(priorityCategories, callSenders, 1074 messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders); 1075 } 1076 isPriorityCategoryEnabled(int categoryType, Policy policy)1077 private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) { 1078 return (policy.priorityCategories & categoryType) != 0; 1079 } 1080 isVisualEffectAllowed(int visualEffect, Policy policy)1081 private boolean isVisualEffectAllowed(int visualEffect, Policy policy) { 1082 return (policy.suppressedVisualEffects & visualEffect) == 0; 1083 } 1084 getNotificationPolicySenders(@enPolicy.PeopleType int senders, int defaultPolicySender)1085 private int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders, 1086 int defaultPolicySender) { 1087 switch (senders) { 1088 case ZenPolicy.PEOPLE_TYPE_ANYONE: 1089 return Policy.PRIORITY_SENDERS_ANY; 1090 case ZenPolicy.PEOPLE_TYPE_CONTACTS: 1091 return Policy.PRIORITY_SENDERS_CONTACTS; 1092 case ZenPolicy.PEOPLE_TYPE_STARRED: 1093 return Policy.PRIORITY_SENDERS_STARRED; 1094 default: 1095 return defaultPolicySender; 1096 } 1097 } 1098 getConversationSendersWithDefault(@enPolicy.ConversationSenders int senders, int defaultPolicySender)1099 private int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders, 1100 int defaultPolicySender) { 1101 switch (senders) { 1102 case ZenPolicy.CONVERSATION_SENDERS_ANYONE: 1103 case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT: 1104 case ZenPolicy.CONVERSATION_SENDERS_NONE: 1105 return senders; 1106 default: 1107 return defaultPolicySender; 1108 } 1109 } 1110 1111 /** 1112 * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType 1113 */ getZenPolicySenders(int senders)1114 public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) { 1115 switch (senders) { 1116 case Policy.PRIORITY_SENDERS_ANY: 1117 return ZenPolicy.PEOPLE_TYPE_ANYONE; 1118 case Policy.PRIORITY_SENDERS_CONTACTS: 1119 return ZenPolicy.PEOPLE_TYPE_CONTACTS; 1120 case Policy.PRIORITY_SENDERS_STARRED: 1121 default: 1122 return ZenPolicy.PEOPLE_TYPE_STARRED; 1123 } 1124 } 1125 toNotificationPolicy()1126 public Policy toNotificationPolicy() { 1127 int priorityCategories = 0; 1128 int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; 1129 int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; 1130 int priorityConversationSenders = Policy.CONVERSATION_SENDERS_IMPORTANT; 1131 if (allowConversations) { 1132 priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; 1133 } 1134 if (allowCalls) { 1135 priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; 1136 } 1137 if (allowMessages) { 1138 priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; 1139 } 1140 if (allowEvents) { 1141 priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; 1142 } 1143 if (allowReminders) { 1144 priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; 1145 } 1146 if (allowRepeatCallers) { 1147 priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; 1148 } 1149 if (allowAlarms) { 1150 priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS; 1151 } 1152 if (allowMedia) { 1153 priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA; 1154 } 1155 if (allowSystem) { 1156 priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM; 1157 } 1158 priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); 1159 priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); 1160 priorityConversationSenders = getConversationSendersWithDefault( 1161 allowConversationsFrom, priorityConversationSenders); 1162 1163 return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, 1164 suppressedVisualEffects, areChannelsBypassingDnd 1165 ? Policy.STATE_CHANNELS_BYPASSING_DND : 0, 1166 priorityConversationSenders); 1167 } 1168 1169 /** 1170 * Creates scheduleCalendar from a condition id 1171 * @param conditionId 1172 * @return ScheduleCalendar with info populated with conditionId 1173 */ toScheduleCalendar(Uri conditionId)1174 public static ScheduleCalendar toScheduleCalendar(Uri conditionId) { 1175 final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId); 1176 if (schedule == null || schedule.days == null || schedule.days.length == 0) return null; 1177 final ScheduleCalendar sc = new ScheduleCalendar(); 1178 sc.setSchedule(schedule); 1179 sc.setTimeZone(TimeZone.getDefault()); 1180 return sc; 1181 } 1182 sourceToPrioritySenders(int source, int def)1183 private static int sourceToPrioritySenders(int source, int def) { 1184 switch (source) { 1185 case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY; 1186 case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS; 1187 case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED; 1188 default: return def; 1189 } 1190 } 1191 prioritySendersToSource(int prioritySenders, int def)1192 private static int prioritySendersToSource(int prioritySenders, int def) { 1193 switch (prioritySenders) { 1194 case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; 1195 case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; 1196 case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; 1197 default: return def; 1198 } 1199 } 1200 normalizePrioritySenders(int prioritySenders, int def)1201 private static int normalizePrioritySenders(int prioritySenders, int def) { 1202 if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS 1203 || prioritySenders == Policy.PRIORITY_SENDERS_STARRED 1204 || prioritySenders == Policy.PRIORITY_SENDERS_ANY)) { 1205 return def; 1206 } 1207 return prioritySenders; 1208 } 1209 normalizeConversationSenders(boolean allowed, int senders, int def)1210 private static int normalizeConversationSenders(boolean allowed, int senders, int def) { 1211 if (!allowed) { 1212 return CONVERSATION_SENDERS_NONE; 1213 } 1214 if (!(senders == CONVERSATION_SENDERS_ANYONE 1215 || senders == CONVERSATION_SENDERS_IMPORTANT 1216 || senders == CONVERSATION_SENDERS_NONE)) { 1217 return def; 1218 } 1219 return senders; 1220 } 1221 applyNotificationPolicy(Policy policy)1222 public void applyNotificationPolicy(Policy policy) { 1223 if (policy == null) return; 1224 allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0; 1225 allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0; 1226 allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0; 1227 allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; 1228 allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; 1229 allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0; 1230 allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; 1231 allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) 1232 != 0; 1233 allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom); 1234 allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders, 1235 allowMessagesFrom); 1236 if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) { 1237 suppressedVisualEffects = policy.suppressedVisualEffects; 1238 } 1239 allowConversations = (policy.priorityCategories 1240 & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0; 1241 allowConversationsFrom = normalizeConversationSenders(allowConversations, 1242 policy.priorityConversationSenders, 1243 allowConversationsFrom); 1244 if (policy.state != Policy.STATE_UNSET) { 1245 areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; 1246 } 1247 } 1248 toTimeCondition(Context context, int minutesFromNow, int userHandle)1249 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { 1250 return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/); 1251 } 1252 toTimeCondition(Context context, int minutesFromNow, int userHandle, boolean shortVersion)1253 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle, 1254 boolean shortVersion) { 1255 final long now = System.currentTimeMillis(); 1256 final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; 1257 return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion); 1258 } 1259 toTimeCondition(Context context, long time, int minutes, int userHandle, boolean shortVersion)1260 public static Condition toTimeCondition(Context context, long time, int minutes, 1261 int userHandle, boolean shortVersion) { 1262 final int num; 1263 String summary, line1, line2; 1264 final CharSequence formattedTime = 1265 getFormattedTime(context, time, isToday(time), userHandle); 1266 final Resources res = context.getResources(); 1267 final Map<String, Object> arguments = new HashMap<>(); 1268 if (minutes < 60) { 1269 // display as minutes 1270 num = minutes; 1271 int summaryResId = shortVersion ? R.string.zen_mode_duration_minutes_summary_short 1272 : R.string.zen_mode_duration_minutes_summary; 1273 arguments.put("count", num); 1274 arguments.put("formattedTime", formattedTime); 1275 summary = PluralsMessageFormatter.format(res, arguments, summaryResId); 1276 int line1ResId = shortVersion ? R.string.zen_mode_duration_minutes_short 1277 : R.string.zen_mode_duration_minutes; 1278 line1 = PluralsMessageFormatter.format(res, arguments, line1ResId); 1279 line2 = res.getString(R.string.zen_mode_until, formattedTime); 1280 } else if (minutes < DAY_MINUTES) { 1281 // display as hours 1282 num = Math.round(minutes / 60f); 1283 int summaryResId = shortVersion ? R.string.zen_mode_duration_hours_summary_short 1284 : R.string.zen_mode_duration_hours_summary; 1285 arguments.put("count", num); 1286 arguments.put("formattedTime", formattedTime); 1287 summary = PluralsMessageFormatter.format(res, arguments, summaryResId); 1288 int line1ResId = shortVersion ? R.string.zen_mode_duration_hours_short 1289 : R.string.zen_mode_duration_hours; 1290 line1 = PluralsMessageFormatter.format(res, arguments, line1ResId); 1291 line2 = res.getString(R.string.zen_mode_until, formattedTime); 1292 } else { 1293 // display as day/time 1294 summary = line1 = line2 = res.getString(R.string.zen_mode_until_next_day, 1295 formattedTime); 1296 } 1297 final Uri id = toCountdownConditionId(time, false); 1298 return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE, 1299 Condition.FLAG_RELEVANT_NOW); 1300 } 1301 1302 /** 1303 * Converts countdown to alarm parameters into a condition with user facing summary 1304 */ toNextAlarmCondition(Context context, long alarm, int userHandle)1305 public static Condition toNextAlarmCondition(Context context, long alarm, 1306 int userHandle) { 1307 boolean isSameDay = isToday(alarm); 1308 final CharSequence formattedTime = getFormattedTime(context, alarm, isSameDay, userHandle); 1309 final Resources res = context.getResources(); 1310 final String line1 = res.getString(R.string.zen_mode_until, formattedTime); 1311 final Uri id = toCountdownConditionId(alarm, true); 1312 return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE, 1313 Condition.FLAG_RELEVANT_NOW); 1314 } 1315 1316 /** 1317 * Creates readable time from time in milliseconds 1318 */ getFormattedTime(Context context, long time, boolean isSameDay, int userHandle)1319 public static CharSequence getFormattedTime(Context context, long time, boolean isSameDay, 1320 int userHandle) { 1321 String skeleton = (!isSameDay ? "EEE " : "") 1322 + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"); 1323 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 1324 return DateFormat.format(pattern, time); 1325 } 1326 1327 /** 1328 * Determines whether a time in milliseconds is today or not 1329 */ isToday(long time)1330 public static boolean isToday(long time) { 1331 GregorianCalendar now = new GregorianCalendar(); 1332 GregorianCalendar endTime = new GregorianCalendar(); 1333 endTime.setTimeInMillis(time); 1334 if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR) 1335 && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH) 1336 && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) { 1337 return true; 1338 } 1339 return false; 1340 } 1341 1342 // ==== Built-in system conditions ==== 1343 1344 public static final String SYSTEM_AUTHORITY = "android"; 1345 1346 // ==== Built-in system condition: countdown ==== 1347 1348 public static final String COUNTDOWN_PATH = "countdown"; 1349 1350 public static final String IS_ALARM_PATH = "alarm"; 1351 1352 /** 1353 * Converts countdown condition parameters into a condition id. 1354 */ toCountdownConditionId(long time, boolean alarm)1355 public static Uri toCountdownConditionId(long time, boolean alarm) { 1356 return new Uri.Builder().scheme(Condition.SCHEME) 1357 .authority(SYSTEM_AUTHORITY) 1358 .appendPath(COUNTDOWN_PATH) 1359 .appendPath(Long.toString(time)) 1360 .appendPath(IS_ALARM_PATH) 1361 .appendPath(Boolean.toString(alarm)) 1362 .build(); 1363 } 1364 tryParseCountdownConditionId(Uri conditionId)1365 public static long tryParseCountdownConditionId(Uri conditionId) { 1366 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; 1367 if (conditionId.getPathSegments().size() < 2 1368 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; 1369 try { 1370 return Long.parseLong(conditionId.getPathSegments().get(1)); 1371 } catch (RuntimeException e) { 1372 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e); 1373 return 0; 1374 } 1375 } 1376 1377 /** 1378 * Returns whether this condition is a countdown condition. 1379 */ isValidCountdownConditionId(Uri conditionId)1380 public static boolean isValidCountdownConditionId(Uri conditionId) { 1381 return tryParseCountdownConditionId(conditionId) != 0; 1382 } 1383 1384 /** 1385 * Returns whether this condition is a countdown to an alarm. 1386 */ isValidCountdownToAlarmConditionId(Uri conditionId)1387 public static boolean isValidCountdownToAlarmConditionId(Uri conditionId) { 1388 if (tryParseCountdownConditionId(conditionId) != 0) { 1389 if (conditionId.getPathSegments().size() < 4 1390 || !IS_ALARM_PATH.equals(conditionId.getPathSegments().get(2))) { 1391 return false; 1392 } 1393 try { 1394 return Boolean.parseBoolean(conditionId.getPathSegments().get(3)); 1395 } catch (RuntimeException e) { 1396 Slog.w(TAG, "Error parsing countdown alarm condition: " + conditionId, e); 1397 return false; 1398 } 1399 } 1400 return false; 1401 } 1402 1403 // ==== Built-in system condition: schedule ==== 1404 1405 public static final String SCHEDULE_PATH = "schedule"; 1406 toScheduleConditionId(ScheduleInfo schedule)1407 public static Uri toScheduleConditionId(ScheduleInfo schedule) { 1408 return new Uri.Builder().scheme(Condition.SCHEME) 1409 .authority(SYSTEM_AUTHORITY) 1410 .appendPath(SCHEDULE_PATH) 1411 .appendQueryParameter("days", toDayList(schedule.days)) 1412 .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute) 1413 .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute) 1414 .appendQueryParameter("exitAtAlarm", String.valueOf(schedule.exitAtAlarm)) 1415 .build(); 1416 } 1417 isValidScheduleConditionId(Uri conditionId)1418 public static boolean isValidScheduleConditionId(Uri conditionId) { 1419 ScheduleInfo info; 1420 try { 1421 info = tryParseScheduleConditionId(conditionId); 1422 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { 1423 return false; 1424 } 1425 1426 if (info == null || info.days == null || info.days.length == 0) { 1427 return false; 1428 } 1429 return true; 1430 } 1431 1432 /** 1433 * Returns whether the conditionId is a valid ScheduleCondition. 1434 * If allowNever is true, this will return true even if the ScheduleCondition never occurs. 1435 */ isValidScheduleConditionId(Uri conditionId, boolean allowNever)1436 public static boolean isValidScheduleConditionId(Uri conditionId, boolean allowNever) { 1437 ScheduleInfo info; 1438 try { 1439 info = tryParseScheduleConditionId(conditionId); 1440 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { 1441 return false; 1442 } 1443 1444 if (info == null || (!allowNever && (info.days == null || info.days.length == 0))) { 1445 return false; 1446 } 1447 return true; 1448 } 1449 1450 @UnsupportedAppUsage tryParseScheduleConditionId(Uri conditionId)1451 public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { 1452 final boolean isSchedule = conditionId != null 1453 && Condition.SCHEME.equals(conditionId.getScheme()) 1454 && ZenModeConfig.SYSTEM_AUTHORITY.equals(conditionId.getAuthority()) 1455 && conditionId.getPathSegments().size() == 1 1456 && ZenModeConfig.SCHEDULE_PATH.equals(conditionId.getPathSegments().get(0)); 1457 if (!isSchedule) return null; 1458 final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); 1459 final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); 1460 if (start == null || end == null) return null; 1461 final ScheduleInfo rt = new ScheduleInfo(); 1462 rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\."); 1463 rt.startHour = start[0]; 1464 rt.startMinute = start[1]; 1465 rt.endHour = end[0]; 1466 rt.endMinute = end[1]; 1467 rt.exitAtAlarm = safeBoolean(conditionId.getQueryParameter("exitAtAlarm"), false); 1468 return rt; 1469 } 1470 getScheduleConditionProvider()1471 public static ComponentName getScheduleConditionProvider() { 1472 return new ComponentName(SYSTEM_AUTHORITY, "ScheduleConditionProvider"); 1473 } 1474 1475 public static class ScheduleInfo { 1476 @UnsupportedAppUsage 1477 public int[] days; 1478 @UnsupportedAppUsage 1479 public int startHour; 1480 @UnsupportedAppUsage 1481 public int startMinute; 1482 @UnsupportedAppUsage 1483 public int endHour; 1484 @UnsupportedAppUsage 1485 public int endMinute; 1486 public boolean exitAtAlarm; 1487 public long nextAlarm; 1488 1489 @Override hashCode()1490 public int hashCode() { 1491 return 0; 1492 } 1493 1494 @Override equals(@ullable Object o)1495 public boolean equals(@Nullable Object o) { 1496 if (!(o instanceof ScheduleInfo)) return false; 1497 final ScheduleInfo other = (ScheduleInfo) o; 1498 return toDayList(days).equals(toDayList(other.days)) 1499 && startHour == other.startHour 1500 && startMinute == other.startMinute 1501 && endHour == other.endHour 1502 && endMinute == other.endMinute 1503 && exitAtAlarm == other.exitAtAlarm; 1504 } 1505 copy()1506 public ScheduleInfo copy() { 1507 final ScheduleInfo rt = new ScheduleInfo(); 1508 if (days != null) { 1509 rt.days = new int[days.length]; 1510 System.arraycopy(days, 0, rt.days, 0, days.length); 1511 } 1512 rt.startHour = startHour; 1513 rt.startMinute = startMinute; 1514 rt.endHour = endHour; 1515 rt.endMinute = endMinute; 1516 rt.exitAtAlarm = exitAtAlarm; 1517 rt.nextAlarm = nextAlarm; 1518 return rt; 1519 } 1520 1521 @Override toString()1522 public String toString() { 1523 return "ScheduleInfo{" + 1524 "days=" + Arrays.toString(days) + 1525 ", startHour=" + startHour + 1526 ", startMinute=" + startMinute + 1527 ", endHour=" + endHour + 1528 ", endMinute=" + endMinute + 1529 ", exitAtAlarm=" + exitAtAlarm + 1530 ", nextAlarm=" + ts(nextAlarm) + 1531 '}'; 1532 } 1533 ts(long time)1534 protected static String ts(long time) { 1535 return new Date(time) + " (" + time + ")"; 1536 } 1537 } 1538 1539 // ==== Built-in system condition: event ==== 1540 1541 public static final String EVENT_PATH = "event"; 1542 toEventConditionId(EventInfo event)1543 public static Uri toEventConditionId(EventInfo event) { 1544 return new Uri.Builder().scheme(Condition.SCHEME) 1545 .authority(SYSTEM_AUTHORITY) 1546 .appendPath(EVENT_PATH) 1547 .appendQueryParameter("userId", Long.toString(event.userId)) 1548 .appendQueryParameter("calendar", event.calName != null ? event.calName : "") 1549 .appendQueryParameter("calendarId", event.calendarId != null 1550 ? event.calendarId.toString() : "") 1551 .appendQueryParameter("reply", Integer.toString(event.reply)) 1552 .build(); 1553 } 1554 isValidEventConditionId(Uri conditionId)1555 public static boolean isValidEventConditionId(Uri conditionId) { 1556 return tryParseEventConditionId(conditionId) != null; 1557 } 1558 tryParseEventConditionId(Uri conditionId)1559 public static EventInfo tryParseEventConditionId(Uri conditionId) { 1560 final boolean isEvent = conditionId != null 1561 && Condition.SCHEME.equals(conditionId.getScheme()) 1562 && ZenModeConfig.SYSTEM_AUTHORITY.equals(conditionId.getAuthority()) 1563 && conditionId.getPathSegments().size() == 1 1564 && EVENT_PATH.equals(conditionId.getPathSegments().get(0)); 1565 if (!isEvent) return null; 1566 final EventInfo rt = new EventInfo(); 1567 rt.userId = tryParseInt(conditionId.getQueryParameter("userId"), UserHandle.USER_NULL); 1568 rt.calName = conditionId.getQueryParameter("calendar"); 1569 if (TextUtils.isEmpty(rt.calName)) { 1570 rt.calName = null; 1571 } 1572 rt.calendarId = tryParseLong(conditionId.getQueryParameter("calendarId"), null); 1573 rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); 1574 return rt; 1575 } 1576 getEventConditionProvider()1577 public static ComponentName getEventConditionProvider() { 1578 return new ComponentName(SYSTEM_AUTHORITY, "EventConditionProvider"); 1579 } 1580 1581 public static class EventInfo { 1582 public static final int REPLY_ANY_EXCEPT_NO = 0; 1583 public static final int REPLY_YES_OR_MAYBE = 1; 1584 public static final int REPLY_YES = 2; 1585 1586 public int userId = UserHandle.USER_NULL; // USER_NULL = unspecified - use current user 1587 public String calName; // CalendarContract.Calendars.DISPLAY_NAME, or null for any 1588 public Long calendarId; // Calendars._ID, or null if restored from < Q calendar 1589 public int reply; 1590 1591 @Override hashCode()1592 public int hashCode() { 1593 return Objects.hash(userId, calName, calendarId, reply); 1594 } 1595 1596 @Override equals(@ullable Object o)1597 public boolean equals(@Nullable Object o) { 1598 if (!(o instanceof EventInfo)) return false; 1599 final EventInfo other = (EventInfo) o; 1600 return userId == other.userId 1601 && Objects.equals(calName, other.calName) 1602 && reply == other.reply 1603 && Objects.equals(calendarId, other.calendarId); 1604 } 1605 copy()1606 public EventInfo copy() { 1607 final EventInfo rt = new EventInfo(); 1608 rt.userId = userId; 1609 rt.calName = calName; 1610 rt.reply = reply; 1611 rt.calendarId = calendarId; 1612 return rt; 1613 } 1614 resolveUserId(int userId)1615 public static int resolveUserId(int userId) { 1616 return userId == UserHandle.USER_NULL ? ActivityManager.getCurrentUser() : userId; 1617 } 1618 } 1619 1620 // ==== End built-in system conditions ==== 1621 tryParseHourAndMinute(String value)1622 private static int[] tryParseHourAndMinute(String value) { 1623 if (TextUtils.isEmpty(value)) return null; 1624 final int i = value.indexOf('.'); 1625 if (i < 1 || i >= value.length() - 1) return null; 1626 final int hour = tryParseInt(value.substring(0, i), -1); 1627 final int minute = tryParseInt(value.substring(i + 1), -1); 1628 return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; 1629 } 1630 tryParseZenMode(String value, int defValue)1631 private static int tryParseZenMode(String value, int defValue) { 1632 final int rt = tryParseInt(value, defValue); 1633 return Global.isValidZenMode(rt) ? rt : defValue; 1634 } 1635 newRuleId()1636 public static String newRuleId() { 1637 return UUID.randomUUID().toString().replace("-", ""); 1638 } 1639 1640 /** 1641 * Gets the name of the app associated with owner 1642 */ getOwnerCaption(Context context, String owner)1643 public static String getOwnerCaption(Context context, String owner) { 1644 final PackageManager pm = context.getPackageManager(); 1645 try { 1646 final ApplicationInfo info = pm.getApplicationInfo(owner, 0); 1647 if (info != null) { 1648 final CharSequence seq = info.loadLabel(pm); 1649 if (seq != null) { 1650 final String str = seq.toString().trim(); 1651 if (str.length() > 0) { 1652 return str; 1653 } 1654 } 1655 } 1656 } catch (Throwable e) { 1657 Slog.w(TAG, "Error loading owner caption", e); 1658 } 1659 return ""; 1660 } 1661 getConditionSummary(Context context, ZenModeConfig config, int userHandle, boolean shortVersion)1662 public static String getConditionSummary(Context context, ZenModeConfig config, 1663 int userHandle, boolean shortVersion) { 1664 return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion); 1665 } 1666 getConditionLine(Context context, ZenModeConfig config, int userHandle, boolean useLine1, boolean shortVersion)1667 private static String getConditionLine(Context context, ZenModeConfig config, 1668 int userHandle, boolean useLine1, boolean shortVersion) { 1669 if (config == null) return ""; 1670 String summary = ""; 1671 if (config.manualRule != null) { 1672 final Uri id = config.manualRule.conditionId; 1673 if (config.manualRule.enabler != null) { 1674 summary = getOwnerCaption(context, config.manualRule.enabler); 1675 } else { 1676 if (id == null) { 1677 summary = context.getString(com.android.internal.R.string.zen_mode_forever); 1678 } else { 1679 final long time = tryParseCountdownConditionId(id); 1680 Condition c = config.manualRule.condition; 1681 if (time > 0) { 1682 final long now = System.currentTimeMillis(); 1683 final long span = time - now; 1684 c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS), 1685 userHandle, shortVersion); 1686 } 1687 final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary; 1688 summary = TextUtils.isEmpty(rt) ? "" : rt; 1689 } 1690 } 1691 } 1692 for (ZenRule automaticRule : config.automaticRules.values()) { 1693 if (automaticRule.isAutomaticActive()) { 1694 if (summary.isEmpty()) { 1695 summary = automaticRule.name; 1696 } else { 1697 summary = context.getResources() 1698 .getString(R.string.zen_mode_rule_name_combination, summary, 1699 automaticRule.name); 1700 } 1701 1702 } 1703 } 1704 return summary; 1705 } 1706 1707 public static class ZenRule implements Parcelable { 1708 @UnsupportedAppUsage 1709 public boolean enabled; 1710 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1711 public boolean snoozing; // user manually disabled this instance 1712 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1713 public String name; // required for automatic 1714 @UnsupportedAppUsage 1715 public int zenMode; // ie: Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 1716 @UnsupportedAppUsage 1717 public Uri conditionId; // required for automatic 1718 public Condition condition; // optional 1719 public ComponentName component; // optional 1720 public ComponentName configurationActivity; // optional 1721 public String id; // required for automatic (unique) 1722 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1723 public long creationTime; // required for automatic 1724 // package name, only used for manual rules when they have turned DND on. 1725 public String enabler; 1726 public ZenPolicy zenPolicy; 1727 public boolean modified; // rule has been modified from initial creation 1728 public String pkg; 1729 ZenRule()1730 public ZenRule() { } 1731 ZenRule(Parcel source)1732 public ZenRule(Parcel source) { 1733 enabled = source.readInt() == 1; 1734 snoozing = source.readInt() == 1; 1735 if (source.readInt() == 1) { 1736 name = source.readString(); 1737 } 1738 zenMode = source.readInt(); 1739 conditionId = source.readParcelable(null, android.net.Uri.class); 1740 condition = source.readParcelable(null, android.service.notification.Condition.class); 1741 component = source.readParcelable(null, android.content.ComponentName.class); 1742 configurationActivity = source.readParcelable(null, android.content.ComponentName.class); 1743 if (source.readInt() == 1) { 1744 id = source.readString(); 1745 } 1746 creationTime = source.readLong(); 1747 if (source.readInt() == 1) { 1748 enabler = source.readString(); 1749 } 1750 zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); 1751 modified = source.readInt() == 1; 1752 pkg = source.readString(); 1753 } 1754 1755 @Override describeContents()1756 public int describeContents() { 1757 return 0; 1758 } 1759 1760 @Override writeToParcel(Parcel dest, int flags)1761 public void writeToParcel(Parcel dest, int flags) { 1762 dest.writeInt(enabled ? 1 : 0); 1763 dest.writeInt(snoozing ? 1 : 0); 1764 if (name != null) { 1765 dest.writeInt(1); 1766 dest.writeString(name); 1767 } else { 1768 dest.writeInt(0); 1769 } 1770 dest.writeInt(zenMode); 1771 dest.writeParcelable(conditionId, 0); 1772 dest.writeParcelable(condition, 0); 1773 dest.writeParcelable(component, 0); 1774 dest.writeParcelable(configurationActivity, 0); 1775 if (id != null) { 1776 dest.writeInt(1); 1777 dest.writeString(id); 1778 } else { 1779 dest.writeInt(0); 1780 } 1781 dest.writeLong(creationTime); 1782 if (enabler != null) { 1783 dest.writeInt(1); 1784 dest.writeString(enabler); 1785 } else { 1786 dest.writeInt(0); 1787 } 1788 dest.writeParcelable(zenPolicy, 0); 1789 dest.writeInt(modified ? 1 : 0); 1790 dest.writeString(pkg); 1791 } 1792 1793 @Override toString()1794 public String toString() { 1795 return new StringBuilder(ZenRule.class.getSimpleName()).append('[') 1796 .append("id=").append(id) 1797 .append(",state=").append(condition == null ? "STATE_FALSE" 1798 : Condition.stateToString(condition.state)) 1799 .append(",enabled=").append(String.valueOf(enabled).toUpperCase()) 1800 .append(",snoozing=").append(snoozing) 1801 .append(",name=").append(name) 1802 .append(",zenMode=").append(Global.zenModeToString(zenMode)) 1803 .append(",conditionId=").append(conditionId) 1804 .append(",pkg=").append(pkg) 1805 .append(",component=").append(component) 1806 .append(",configActivity=").append(configurationActivity) 1807 .append(",creationTime=").append(creationTime) 1808 .append(",enabler=").append(enabler) 1809 .append(",zenPolicy=").append(zenPolicy) 1810 .append(",modified=").append(modified) 1811 .append(",condition=").append(condition) 1812 .append(']').toString(); 1813 } 1814 1815 /** @hide */ 1816 // TODO: add configuration activity dumpDebug(ProtoOutputStream proto, long fieldId)1817 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 1818 final long token = proto.start(fieldId); 1819 1820 proto.write(ZenRuleProto.ID, id); 1821 proto.write(ZenRuleProto.NAME, name); 1822 proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime); 1823 proto.write(ZenRuleProto.ENABLED, enabled); 1824 proto.write(ZenRuleProto.ENABLER, enabler); 1825 proto.write(ZenRuleProto.IS_SNOOZING, snoozing); 1826 proto.write(ZenRuleProto.ZEN_MODE, zenMode); 1827 if (conditionId != null) { 1828 proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString()); 1829 } 1830 if (condition != null) { 1831 condition.dumpDebug(proto, ZenRuleProto.CONDITION); 1832 } 1833 if (component != null) { 1834 component.dumpDebug(proto, ZenRuleProto.COMPONENT); 1835 } 1836 if (zenPolicy != null) { 1837 zenPolicy.dumpDebug(proto, ZenRuleProto.ZEN_POLICY); 1838 } 1839 proto.write(ZenRuleProto.MODIFIED, modified); 1840 proto.end(token); 1841 } 1842 1843 @Override equals(@ullable Object o)1844 public boolean equals(@Nullable Object o) { 1845 if (!(o instanceof ZenRule)) return false; 1846 if (o == this) return true; 1847 final ZenRule other = (ZenRule) o; 1848 return other.enabled == enabled 1849 && other.snoozing == snoozing 1850 && Objects.equals(other.name, name) 1851 && other.zenMode == zenMode 1852 && Objects.equals(other.conditionId, conditionId) 1853 && Objects.equals(other.condition, condition) 1854 && Objects.equals(other.component, component) 1855 && Objects.equals(other.configurationActivity, configurationActivity) 1856 && Objects.equals(other.id, id) 1857 && Objects.equals(other.enabler, enabler) 1858 && Objects.equals(other.zenPolicy, zenPolicy) 1859 && Objects.equals(other.pkg, pkg) 1860 && other.modified == modified; 1861 } 1862 1863 @Override hashCode()1864 public int hashCode() { 1865 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, 1866 component, configurationActivity, pkg, id, enabler, zenPolicy, modified); 1867 } 1868 isAutomaticActive()1869 public boolean isAutomaticActive() { 1870 return enabled && !snoozing && getPkg() != null && isTrueOrUnknown(); 1871 } 1872 getPkg()1873 public String getPkg() { 1874 return !TextUtils.isEmpty(pkg) 1875 ? pkg 1876 : (component != null) 1877 ? component.getPackageName() 1878 : (configurationActivity != null) 1879 ? configurationActivity.getPackageName() 1880 : null; 1881 } 1882 isTrueOrUnknown()1883 public boolean isTrueOrUnknown() { 1884 return condition != null && (condition.state == Condition.STATE_TRUE 1885 || condition.state == Condition.STATE_UNKNOWN); 1886 } 1887 1888 public static final @android.annotation.NonNull Parcelable.Creator<ZenRule> CREATOR 1889 = new Parcelable.Creator<ZenRule>() { 1890 @Override 1891 public ZenRule createFromParcel(Parcel source) { 1892 return new ZenRule(source); 1893 } 1894 @Override 1895 public ZenRule[] newArray(int size) { 1896 return new ZenRule[size]; 1897 } 1898 }; 1899 } 1900 1901 /** 1902 * Determines whether dnd behavior should mute all ringer-controlled sounds 1903 * This includes notification, ringer and system sounds 1904 */ areAllPriorityOnlyRingerSoundsMuted(NotificationManager.Policy policy)1905 public static boolean areAllPriorityOnlyRingerSoundsMuted(NotificationManager.Policy 1906 policy) { 1907 boolean allowReminders = (policy.priorityCategories 1908 & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0; 1909 boolean allowCalls = (policy.priorityCategories 1910 & NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) != 0; 1911 boolean allowMessages = (policy.priorityCategories 1912 & NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) != 0; 1913 boolean allowEvents = (policy.priorityCategories 1914 & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0; 1915 boolean allowRepeatCallers = (policy.priorityCategories 1916 & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; 1917 boolean allowConversations = (policy.priorityConversationSenders 1918 & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0; 1919 boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; 1920 boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0; 1921 return !allowReminders && !allowCalls && !allowMessages && !allowEvents 1922 && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem 1923 && !allowConversations; 1924 } 1925 1926 /** 1927 * Determines whether dnd behavior should mute all sounds 1928 */ areAllZenBehaviorSoundsMuted(NotificationManager.Policy policy)1929 public static boolean areAllZenBehaviorSoundsMuted(NotificationManager.Policy 1930 policy) { 1931 boolean allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0; 1932 boolean allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0; 1933 return !allowAlarms && !allowMedia && areAllPriorityOnlyRingerSoundsMuted(policy); 1934 } 1935 1936 /** 1937 * Determines if DND is currently overriding the ringer 1938 */ isZenOverridingRinger(int zen, Policy consolidatedPolicy)1939 public static boolean isZenOverridingRinger(int zen, Policy consolidatedPolicy) { 1940 return zen == Global.ZEN_MODE_NO_INTERRUPTIONS 1941 || zen == Global.ZEN_MODE_ALARMS 1942 || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS 1943 && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(consolidatedPolicy)); 1944 } 1945 1946 /** 1947 * Determines whether dnd behavior should mute all ringer-controlled sounds 1948 * This includes notification, ringer and system sounds 1949 */ areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config)1950 public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) { 1951 return !config.allowReminders && !config.allowCalls && !config.allowMessages 1952 && !config.allowEvents && !config.allowRepeatCallers 1953 && !config.areChannelsBypassingDnd && !config.allowSystem; 1954 } 1955 1956 /** 1957 * Determines whether dnd mutes all sounds 1958 */ areAllZenBehaviorSoundsMuted(ZenModeConfig config)1959 public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) { 1960 return !config.allowAlarms && !config.allowMedia 1961 && areAllPriorityOnlyRingerSoundsMuted(config); 1962 } 1963 1964 /** 1965 * Returns a description of the current do not disturb settings from config. 1966 * - If turned on manually and end time is known, returns end time. 1967 * - If turned on manually and end time is on forever until turned off, return null if 1968 * describeForeverCondition is false, else return String describing indefinite behavior 1969 * - If turned on by an automatic rule, returns the automatic rule name. 1970 * - If on due to an app, returns the app name. 1971 * - If there's a combination of rules/apps that trigger, then shows the one that will 1972 * last the longest if applicable. 1973 * @return null if DND is off or describeForeverCondition is false and 1974 * DND is on forever (until turned off) 1975 */ getDescription(Context context, boolean zenOn, ZenModeConfig config, boolean describeForeverCondition)1976 public static String getDescription(Context context, boolean zenOn, ZenModeConfig config, 1977 boolean describeForeverCondition) { 1978 if (!zenOn || config == null) { 1979 return null; 1980 } 1981 1982 String secondaryText = ""; 1983 long latestEndTime = -1; 1984 1985 // DND turned on by manual rule 1986 if (config.manualRule != null) { 1987 final Uri id = config.manualRule.conditionId; 1988 if (config.manualRule.enabler != null) { 1989 // app triggered manual rule 1990 String appName = getOwnerCaption(context, config.manualRule.enabler); 1991 if (!appName.isEmpty()) { 1992 secondaryText = appName; 1993 } 1994 } else { 1995 if (id == null) { 1996 // Do not disturb manually triggered to remain on forever until turned off 1997 if (describeForeverCondition) { 1998 return context.getString(R.string.zen_mode_forever); 1999 } else { 2000 return null; 2001 } 2002 } else { 2003 latestEndTime = tryParseCountdownConditionId(id); 2004 if (latestEndTime > 0) { 2005 final CharSequence formattedTime = getFormattedTime(context, 2006 latestEndTime, isToday(latestEndTime), 2007 context.getUserId()); 2008 secondaryText = context.getString(R.string.zen_mode_until, formattedTime); 2009 } 2010 } 2011 } 2012 } 2013 2014 // DND turned on by an automatic rule 2015 for (ZenRule automaticRule : config.automaticRules.values()) { 2016 if (automaticRule.isAutomaticActive()) { 2017 if (isValidEventConditionId(automaticRule.conditionId) 2018 || isValidScheduleConditionId(automaticRule.conditionId)) { 2019 // set text if automatic rule end time is the latest active rule end time 2020 long endTime = parseAutomaticRuleEndTime(context, automaticRule.conditionId); 2021 if (endTime > latestEndTime) { 2022 latestEndTime = endTime; 2023 secondaryText = automaticRule.name; 2024 } 2025 } else { 2026 // set text if 3rd party rule 2027 return automaticRule.name; 2028 } 2029 } 2030 } 2031 2032 return !secondaryText.equals("") ? secondaryText : null; 2033 } 2034 parseAutomaticRuleEndTime(Context context, Uri id)2035 private static long parseAutomaticRuleEndTime(Context context, Uri id) { 2036 if (isValidEventConditionId(id)) { 2037 // cannot look up end times for events 2038 return Long.MAX_VALUE; 2039 } 2040 2041 if (isValidScheduleConditionId(id)) { 2042 ScheduleCalendar schedule = toScheduleCalendar(id); 2043 long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis()); 2044 2045 // check if automatic rule will end on next alarm 2046 if (schedule.exitAtAlarm()) { 2047 long nextAlarm = getNextAlarm(context); 2048 schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm); 2049 if (schedule.shouldExitForAlarm(endTimeMs)) { 2050 return nextAlarm; 2051 } 2052 } 2053 2054 return endTimeMs; 2055 } 2056 2057 return -1; 2058 } 2059 getNextAlarm(Context context)2060 private static long getNextAlarm(Context context) { 2061 final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 2062 final AlarmManager.AlarmClockInfo info = alarms.getNextAlarmClock(context.getUserId()); 2063 return info != null ? info.getTriggerTime() : 0; 2064 } 2065 } 2066