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