1 /** 2 * Copyright (c) 2014, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.notification; 18 19 import android.app.ActivityManager; 20 import android.app.NotificationManager.Policy; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.net.Uri; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.os.UserHandle; 28 import android.provider.Settings.Global; 29 import android.text.TextUtils; 30 import android.text.format.DateFormat; 31 import android.util.ArrayMap; 32 import android.util.ArraySet; 33 import android.util.Slog; 34 35 import com.android.internal.R; 36 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 import org.xmlpull.v1.XmlSerializer; 40 41 import java.io.IOException; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Calendar; 45 import java.util.GregorianCalendar; 46 import java.util.Locale; 47 import java.util.Objects; 48 import java.util.UUID; 49 50 /** 51 * Persisted configuration for zen mode. 52 * 53 * @hide 54 */ 55 public class ZenModeConfig implements Parcelable { 56 private static String TAG = "ZenModeConfig"; 57 58 public static final int SOURCE_ANYONE = 0; 59 public static final int SOURCE_CONTACT = 1; 60 public static final int SOURCE_STAR = 2; 61 public static final int MAX_SOURCE = SOURCE_STAR; 62 private static final int DEFAULT_SOURCE = SOURCE_CONTACT; 63 64 public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 65 Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; 66 public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 67 Calendar.WEDNESDAY, Calendar.THURSDAY }; 68 public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY }; 69 70 public static final int[] MINUTE_BUCKETS = generateMinuteBuckets(); 71 private static final int SECONDS_MS = 1000; 72 private static final int MINUTES_MS = 60 * SECONDS_MS; 73 private static final int DAY_MINUTES = 24 * 60; 74 private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; 75 76 private static final boolean DEFAULT_ALLOW_CALLS = true; 77 private static final boolean DEFAULT_ALLOW_MESSAGES = false; 78 private static final boolean DEFAULT_ALLOW_REMINDERS = true; 79 private static final boolean DEFAULT_ALLOW_EVENTS = true; 80 private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; 81 private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true; 82 private static final boolean DEFAULT_ALLOW_SCREEN_ON = true; 83 84 private static final int XML_VERSION = 2; 85 private static final String ZEN_TAG = "zen"; 86 private static final String ZEN_ATT_VERSION = "version"; 87 private static final String ZEN_ATT_USER = "user"; 88 private static final String ALLOW_TAG = "allow"; 89 private static final String ALLOW_ATT_CALLS = "calls"; 90 private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; 91 private static final String ALLOW_ATT_MESSAGES = "messages"; 92 private static final String ALLOW_ATT_FROM = "from"; 93 private static final String ALLOW_ATT_CALLS_FROM = "callsFrom"; 94 private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom"; 95 private static final String ALLOW_ATT_REMINDERS = "reminders"; 96 private static final String ALLOW_ATT_EVENTS = "events"; 97 private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff"; 98 private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; 99 100 private static final String CONDITION_TAG = "condition"; 101 private static final String CONDITION_ATT_COMPONENT = "component"; 102 private static final String CONDITION_ATT_ID = "id"; 103 private static final String CONDITION_ATT_SUMMARY = "summary"; 104 private static final String CONDITION_ATT_LINE1 = "line1"; 105 private static final String CONDITION_ATT_LINE2 = "line2"; 106 private static final String CONDITION_ATT_ICON = "icon"; 107 private static final String CONDITION_ATT_STATE = "state"; 108 private static final String CONDITION_ATT_FLAGS = "flags"; 109 110 private static final String MANUAL_TAG = "manual"; 111 private static final String AUTOMATIC_TAG = "automatic"; 112 113 private static final String RULE_ATT_ID = "ruleId"; 114 private static final String RULE_ATT_ENABLED = "enabled"; 115 private static final String RULE_ATT_SNOOZING = "snoozing"; 116 private static final String RULE_ATT_NAME = "name"; 117 private static final String RULE_ATT_COMPONENT = "component"; 118 private static final String RULE_ATT_ZEN = "zen"; 119 private static final String RULE_ATT_CONDITION_ID = "conditionId"; 120 private static final String RULE_ATT_CREATION_TIME = "creationTime"; 121 122 public boolean allowCalls = DEFAULT_ALLOW_CALLS; 123 public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; 124 public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; 125 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 126 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 127 public int allowCallsFrom = DEFAULT_SOURCE; 128 public int allowMessagesFrom = DEFAULT_SOURCE; 129 public int user = UserHandle.USER_SYSTEM; 130 public boolean allowWhenScreenOff = DEFAULT_ALLOW_SCREEN_OFF; 131 public boolean allowWhenScreenOn = DEFAULT_ALLOW_SCREEN_ON; 132 133 public ZenRule manualRule; 134 public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); 135 ZenModeConfig()136 public ZenModeConfig() { } 137 ZenModeConfig(Parcel source)138 public ZenModeConfig(Parcel source) { 139 allowCalls = source.readInt() == 1; 140 allowRepeatCallers = source.readInt() == 1; 141 allowMessages = source.readInt() == 1; 142 allowReminders = source.readInt() == 1; 143 allowEvents = source.readInt() == 1; 144 allowCallsFrom = source.readInt(); 145 allowMessagesFrom = source.readInt(); 146 user = source.readInt(); 147 manualRule = source.readParcelable(null); 148 final int len = source.readInt(); 149 if (len > 0) { 150 final String[] ids = new String[len]; 151 final ZenRule[] rules = new ZenRule[len]; 152 source.readStringArray(ids); 153 source.readTypedArray(rules, ZenRule.CREATOR); 154 for (int i = 0; i < len; i++) { 155 automaticRules.put(ids[i], rules[i]); 156 } 157 } 158 allowWhenScreenOff = source.readInt() == 1; 159 allowWhenScreenOn = source.readInt() == 1; 160 } 161 162 @Override writeToParcel(Parcel dest, int flags)163 public void writeToParcel(Parcel dest, int flags) { 164 dest.writeInt(allowCalls ? 1 : 0); 165 dest.writeInt(allowRepeatCallers ? 1 : 0); 166 dest.writeInt(allowMessages ? 1 : 0); 167 dest.writeInt(allowReminders ? 1 : 0); 168 dest.writeInt(allowEvents ? 1 : 0); 169 dest.writeInt(allowCallsFrom); 170 dest.writeInt(allowMessagesFrom); 171 dest.writeInt(user); 172 dest.writeParcelable(manualRule, 0); 173 if (!automaticRules.isEmpty()) { 174 final int len = automaticRules.size(); 175 final String[] ids = new String[len]; 176 final ZenRule[] rules = new ZenRule[len]; 177 for (int i = 0; i < len; i++) { 178 ids[i] = automaticRules.keyAt(i); 179 rules[i] = automaticRules.valueAt(i); 180 } 181 dest.writeInt(len); 182 dest.writeStringArray(ids); 183 dest.writeTypedArray(rules, 0); 184 } else { 185 dest.writeInt(0); 186 } 187 dest.writeInt(allowWhenScreenOff ? 1 : 0); 188 dest.writeInt(allowWhenScreenOn ? 1 : 0); 189 } 190 191 @Override toString()192 public String toString() { 193 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') 194 .append("user=").append(user) 195 .append(",allowCalls=").append(allowCalls) 196 .append(",allowRepeatCallers=").append(allowRepeatCallers) 197 .append(",allowMessages=").append(allowMessages) 198 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) 199 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) 200 .append(",allowReminders=").append(allowReminders) 201 .append(",allowEvents=").append(allowEvents) 202 .append(",allowWhenScreenOff=").append(allowWhenScreenOff) 203 .append(",allowWhenScreenOn=").append(allowWhenScreenOn) 204 .append(",automaticRules=").append(automaticRules) 205 .append(",manualRule=").append(manualRule) 206 .append(']').toString(); 207 } 208 diff(ZenModeConfig to)209 private Diff diff(ZenModeConfig to) { 210 final Diff d = new Diff(); 211 if (to == null) { 212 return d.addLine("config", "delete"); 213 } 214 if (user != to.user) { 215 d.addLine("user", user, to.user); 216 } 217 if (allowCalls != to.allowCalls) { 218 d.addLine("allowCalls", allowCalls, to.allowCalls); 219 } 220 if (allowRepeatCallers != to.allowRepeatCallers) { 221 d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers); 222 } 223 if (allowMessages != to.allowMessages) { 224 d.addLine("allowMessages", allowMessages, to.allowMessages); 225 } 226 if (allowCallsFrom != to.allowCallsFrom) { 227 d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom); 228 } 229 if (allowMessagesFrom != to.allowMessagesFrom) { 230 d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom); 231 } 232 if (allowReminders != to.allowReminders) { 233 d.addLine("allowReminders", allowReminders, to.allowReminders); 234 } 235 if (allowEvents != to.allowEvents) { 236 d.addLine("allowEvents", allowEvents, to.allowEvents); 237 } 238 if (allowWhenScreenOff != to.allowWhenScreenOff) { 239 d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff); 240 } 241 if (allowWhenScreenOn != to.allowWhenScreenOn) { 242 d.addLine("allowWhenScreenOn", allowWhenScreenOn, to.allowWhenScreenOn); 243 } 244 final ArraySet<String> allRules = new ArraySet<>(); 245 addKeys(allRules, automaticRules); 246 addKeys(allRules, to.automaticRules); 247 final int N = allRules.size(); 248 for (int i = 0; i < N; i++) { 249 final String rule = allRules.valueAt(i); 250 final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null; 251 final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null; 252 ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule); 253 } 254 ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule); 255 return d; 256 } 257 diff(ZenModeConfig from, ZenModeConfig to)258 public static Diff diff(ZenModeConfig from, ZenModeConfig to) { 259 if (from == null) { 260 final Diff d = new Diff(); 261 if (to != null) { 262 d.addLine("config", "insert"); 263 } 264 return d; 265 } 266 return from.diff(to); 267 } 268 addKeys(ArraySet<T> set, ArrayMap<T, ?> map)269 private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) { 270 if (map != null) { 271 for (int i = 0; i < map.size(); i++) { 272 set.add(map.keyAt(i)); 273 } 274 } 275 } 276 isValid()277 public boolean isValid() { 278 if (!isValidManualRule(manualRule)) return false; 279 final int N = automaticRules.size(); 280 for (int i = 0; i < N; i++) { 281 if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false; 282 } 283 return true; 284 } 285 isValidManualRule(ZenRule rule)286 private static boolean isValidManualRule(ZenRule rule) { 287 return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule); 288 } 289 isValidAutomaticRule(ZenRule rule)290 private static boolean isValidAutomaticRule(ZenRule rule) { 291 return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode) 292 && rule.conditionId != null && sameCondition(rule); 293 } 294 sameCondition(ZenRule rule)295 private static boolean sameCondition(ZenRule rule) { 296 if (rule == null) return false; 297 if (rule.conditionId == null) { 298 return rule.condition == null; 299 } else { 300 return rule.condition == null || rule.conditionId.equals(rule.condition.id); 301 } 302 } 303 generateMinuteBuckets()304 private static int[] generateMinuteBuckets() { 305 final int maxHrs = 12; 306 final int[] buckets = new int[maxHrs + 3]; 307 buckets[0] = 15; 308 buckets[1] = 30; 309 buckets[2] = 45; 310 for (int i = 1; i <= maxHrs; i++) { 311 buckets[2 + i] = 60 * i; 312 } 313 return buckets; 314 } 315 sourceToString(int source)316 public static String sourceToString(int source) { 317 switch (source) { 318 case SOURCE_ANYONE: 319 return "anyone"; 320 case SOURCE_CONTACT: 321 return "contacts"; 322 case SOURCE_STAR: 323 return "stars"; 324 default: 325 return "UNKNOWN"; 326 } 327 } 328 329 @Override equals(Object o)330 public boolean equals(Object o) { 331 if (!(o instanceof ZenModeConfig)) return false; 332 if (o == this) return true; 333 final ZenModeConfig other = (ZenModeConfig) o; 334 return other.allowCalls == allowCalls 335 && other.allowRepeatCallers == allowRepeatCallers 336 && other.allowMessages == allowMessages 337 && other.allowCallsFrom == allowCallsFrom 338 && other.allowMessagesFrom == allowMessagesFrom 339 && other.allowReminders == allowReminders 340 && other.allowEvents == allowEvents 341 && other.allowWhenScreenOff == allowWhenScreenOff 342 && other.allowWhenScreenOn == allowWhenScreenOn 343 && other.user == user 344 && Objects.equals(other.automaticRules, automaticRules) 345 && Objects.equals(other.manualRule, manualRule); 346 } 347 348 @Override hashCode()349 public int hashCode() { 350 return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, 351 allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff, 352 allowWhenScreenOn, 353 user, automaticRules, manualRule); 354 } 355 toDayList(int[] days)356 private static String toDayList(int[] days) { 357 if (days == null || days.length == 0) return ""; 358 final StringBuilder sb = new StringBuilder(); 359 for (int i = 0; i < days.length; i++) { 360 if (i > 0) sb.append('.'); 361 sb.append(days[i]); 362 } 363 return sb.toString(); 364 } 365 tryParseDayList(String dayList, String sep)366 private static int[] tryParseDayList(String dayList, String sep) { 367 if (dayList == null) return null; 368 final String[] tokens = dayList.split(sep); 369 if (tokens.length == 0) return null; 370 final int[] rt = new int[tokens.length]; 371 for (int i = 0; i < tokens.length; i++) { 372 final int day = tryParseInt(tokens[i], -1); 373 if (day == -1) return null; 374 rt[i] = day; 375 } 376 return rt; 377 } 378 tryParseInt(String value, int defValue)379 private static int tryParseInt(String value, int defValue) { 380 if (TextUtils.isEmpty(value)) return defValue; 381 try { 382 return Integer.parseInt(value); 383 } catch (NumberFormatException e) { 384 return defValue; 385 } 386 } 387 tryParseLong(String value, long defValue)388 private static long tryParseLong(String value, long defValue) { 389 if (TextUtils.isEmpty(value)) return defValue; 390 try { 391 return Long.valueOf(value); 392 } catch (NumberFormatException e) { 393 return defValue; 394 } 395 } 396 readXml(XmlPullParser parser, Migration migration)397 public static ZenModeConfig readXml(XmlPullParser parser, Migration migration) 398 throws XmlPullParserException, IOException { 399 int type = parser.getEventType(); 400 if (type != XmlPullParser.START_TAG) return null; 401 String tag = parser.getName(); 402 if (!ZEN_TAG.equals(tag)) return null; 403 final ZenModeConfig rt = new ZenModeConfig(); 404 final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION); 405 if (version == 1) { 406 final XmlV1 v1 = XmlV1.readXml(parser); 407 return migration.migrate(v1); 408 } 409 rt.user = safeInt(parser, ZEN_ATT_USER, rt.user); 410 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 411 tag = parser.getName(); 412 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 413 return rt; 414 } 415 if (type == XmlPullParser.START_TAG) { 416 if (ALLOW_TAG.equals(tag)) { 417 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); 418 rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS, 419 DEFAULT_ALLOW_REPEAT_CALLERS); 420 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); 421 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 422 DEFAULT_ALLOW_REMINDERS); 423 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); 424 final int from = safeInt(parser, ALLOW_ATT_FROM, -1); 425 final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); 426 final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); 427 if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { 428 rt.allowCallsFrom = callsFrom; 429 rt.allowMessagesFrom = messagesFrom; 430 } else if (isValidSource(from)) { 431 Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from)); 432 rt.allowCallsFrom = from; 433 rt.allowMessagesFrom = from; 434 } else { 435 rt.allowCallsFrom = DEFAULT_SOURCE; 436 rt.allowMessagesFrom = DEFAULT_SOURCE; 437 } 438 rt.allowWhenScreenOff = 439 safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF); 440 rt.allowWhenScreenOn = 441 safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON); 442 } else if (MANUAL_TAG.equals(tag)) { 443 rt.manualRule = readRuleXml(parser); 444 } else if (AUTOMATIC_TAG.equals(tag)) { 445 final String id = parser.getAttributeValue(null, RULE_ATT_ID); 446 final ZenRule automaticRule = readRuleXml(parser); 447 if (id != null && automaticRule != null) { 448 automaticRule.id = id; 449 rt.automaticRules.put(id, automaticRule); 450 } 451 } 452 } 453 } 454 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 455 } 456 writeXml(XmlSerializer out)457 public void writeXml(XmlSerializer out) throws IOException { 458 out.startTag(null, ZEN_TAG); 459 out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION)); 460 out.attribute(null, ZEN_ATT_USER, Integer.toString(user)); 461 462 out.startTag(null, ALLOW_TAG); 463 out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); 464 out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers)); 465 out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); 466 out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); 467 out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); 468 out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom)); 469 out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom)); 470 out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff)); 471 out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn)); 472 out.endTag(null, ALLOW_TAG); 473 474 if (manualRule != null) { 475 out.startTag(null, MANUAL_TAG); 476 writeRuleXml(manualRule, out); 477 out.endTag(null, MANUAL_TAG); 478 } 479 final int N = automaticRules.size(); 480 for (int i = 0; i < N; i++) { 481 final String id = automaticRules.keyAt(i); 482 final ZenRule automaticRule = automaticRules.valueAt(i); 483 out.startTag(null, AUTOMATIC_TAG); 484 out.attribute(null, RULE_ATT_ID, id); 485 writeRuleXml(automaticRule, out); 486 out.endTag(null, AUTOMATIC_TAG); 487 } 488 out.endTag(null, ZEN_TAG); 489 } 490 readRuleXml(XmlPullParser parser)491 public static ZenRule readRuleXml(XmlPullParser parser) { 492 final ZenRule rt = new ZenRule(); 493 rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true); 494 rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false); 495 rt.name = parser.getAttributeValue(null, RULE_ATT_NAME); 496 final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN); 497 rt.zenMode = tryParseZenMode(zen, -1); 498 if (rt.zenMode == -1) { 499 Slog.w(TAG, "Bad zen mode in rule xml:" + zen); 500 return null; 501 } 502 rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); 503 rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); 504 rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0); 505 rt.condition = readConditionXml(parser); 506 return rt; 507 } 508 writeRuleXml(ZenRule rule, XmlSerializer out)509 public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException { 510 out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled)); 511 out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing)); 512 if (rule.name != null) { 513 out.attribute(null, RULE_ATT_NAME, rule.name); 514 } 515 out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode)); 516 if (rule.component != null) { 517 out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); 518 } 519 if (rule.conditionId != null) { 520 out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString()); 521 } 522 out.attribute(null, RULE_ATT_CREATION_TIME, Long.toString(rule.creationTime)); 523 if (rule.condition != null) { 524 writeConditionXml(rule.condition, out); 525 } 526 } 527 readConditionXml(XmlPullParser parser)528 public static Condition readConditionXml(XmlPullParser parser) { 529 final Uri id = safeUri(parser, CONDITION_ATT_ID); 530 if (id == null) return null; 531 final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); 532 final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); 533 final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); 534 final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); 535 final int state = safeInt(parser, CONDITION_ATT_STATE, -1); 536 final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); 537 try { 538 return new Condition(id, summary, line1, line2, icon, state, flags); 539 } catch (IllegalArgumentException e) { 540 Slog.w(TAG, "Unable to read condition xml", e); 541 return null; 542 } 543 } 544 writeConditionXml(Condition c, XmlSerializer out)545 public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException { 546 out.attribute(null, CONDITION_ATT_ID, c.id.toString()); 547 out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); 548 out.attribute(null, CONDITION_ATT_LINE1, c.line1); 549 out.attribute(null, CONDITION_ATT_LINE2, c.line2); 550 out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon)); 551 out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state)); 552 out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags)); 553 } 554 isValidHour(int val)555 public static boolean isValidHour(int val) { 556 return val >= 0 && val < 24; 557 } 558 isValidMinute(int val)559 public static boolean isValidMinute(int val) { 560 return val >= 0 && val < 60; 561 } 562 isValidSource(int source)563 private static boolean isValidSource(int source) { 564 return source >= SOURCE_ANYONE && source <= MAX_SOURCE; 565 } 566 safeBoolean(XmlPullParser parser, String att, boolean defValue)567 private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { 568 final String val = parser.getAttributeValue(null, att); 569 return safeBoolean(val, defValue); 570 } 571 safeBoolean(String val, boolean defValue)572 private static boolean safeBoolean(String val, boolean defValue) { 573 if (TextUtils.isEmpty(val)) return defValue; 574 return Boolean.valueOf(val); 575 } 576 safeInt(XmlPullParser parser, String att, int defValue)577 private static int safeInt(XmlPullParser parser, String att, int defValue) { 578 final String val = parser.getAttributeValue(null, att); 579 return tryParseInt(val, defValue); 580 } 581 safeComponentName(XmlPullParser parser, String att)582 private static ComponentName safeComponentName(XmlPullParser parser, String att) { 583 final String val = parser.getAttributeValue(null, att); 584 if (TextUtils.isEmpty(val)) return null; 585 return ComponentName.unflattenFromString(val); 586 } 587 safeUri(XmlPullParser parser, String att)588 private static Uri safeUri(XmlPullParser parser, String att) { 589 final String val = parser.getAttributeValue(null, att); 590 if (TextUtils.isEmpty(val)) return null; 591 return Uri.parse(val); 592 } 593 safeLong(XmlPullParser parser, String att, long defValue)594 private static long safeLong(XmlPullParser parser, String att, long defValue) { 595 final String val = parser.getAttributeValue(null, att); 596 return tryParseLong(val, defValue); 597 } 598 599 @Override describeContents()600 public int describeContents() { 601 return 0; 602 } 603 copy()604 public ZenModeConfig copy() { 605 final Parcel parcel = Parcel.obtain(); 606 try { 607 writeToParcel(parcel, 0); 608 parcel.setDataPosition(0); 609 return new ZenModeConfig(parcel); 610 } finally { 611 parcel.recycle(); 612 } 613 } 614 615 public static final Parcelable.Creator<ZenModeConfig> CREATOR 616 = new Parcelable.Creator<ZenModeConfig>() { 617 @Override 618 public ZenModeConfig createFromParcel(Parcel source) { 619 return new ZenModeConfig(source); 620 } 621 622 @Override 623 public ZenModeConfig[] newArray(int size) { 624 return new ZenModeConfig[size]; 625 } 626 }; 627 toNotificationPolicy()628 public Policy toNotificationPolicy() { 629 int priorityCategories = 0; 630 int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; 631 int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; 632 if (allowCalls) { 633 priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; 634 } 635 if (allowMessages) { 636 priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; 637 } 638 if (allowEvents) { 639 priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; 640 } 641 if (allowReminders) { 642 priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; 643 } 644 if (allowRepeatCallers) { 645 priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; 646 } 647 int suppressedVisualEffects = 0; 648 if (!allowWhenScreenOff) { 649 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_OFF; 650 } 651 if (!allowWhenScreenOn) { 652 suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON; 653 } 654 priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); 655 priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); 656 return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, 657 suppressedVisualEffects); 658 } 659 sourceToPrioritySenders(int source, int def)660 private static int sourceToPrioritySenders(int source, int def) { 661 switch (source) { 662 case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY; 663 case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS; 664 case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED; 665 default: return def; 666 } 667 } 668 prioritySendersToSource(int prioritySenders, int def)669 private static int prioritySendersToSource(int prioritySenders, int def) { 670 switch (prioritySenders) { 671 case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; 672 case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; 673 case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; 674 default: return def; 675 } 676 } 677 applyNotificationPolicy(Policy policy)678 public void applyNotificationPolicy(Policy policy) { 679 if (policy == null) return; 680 allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0; 681 allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; 682 allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; 683 allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; 684 allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) 685 != 0; 686 allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom); 687 allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders, 688 allowMessagesFrom); 689 if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) { 690 allowWhenScreenOff = 691 (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_SCREEN_OFF) == 0; 692 allowWhenScreenOn = 693 (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_SCREEN_ON) == 0; 694 } 695 } 696 toTimeCondition(Context context, int minutesFromNow, int userHandle)697 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { 698 return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/); 699 } 700 toTimeCondition(Context context, int minutesFromNow, int userHandle, boolean shortVersion)701 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle, 702 boolean shortVersion) { 703 final long now = System.currentTimeMillis(); 704 final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; 705 return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion); 706 } 707 toTimeCondition(Context context, long time, int minutes, int userHandle, boolean shortVersion)708 public static Condition toTimeCondition(Context context, long time, int minutes, 709 int userHandle, boolean shortVersion) { 710 final int num; 711 String summary, line1, line2; 712 final CharSequence formattedTime = getFormattedTime(context, time, userHandle); 713 final Resources res = context.getResources(); 714 if (minutes < 60) { 715 // display as minutes 716 num = minutes; 717 int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short 718 : R.plurals.zen_mode_duration_minutes_summary; 719 summary = res.getQuantityString(summaryResId, num, num, formattedTime); 720 int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short 721 : R.plurals.zen_mode_duration_minutes; 722 line1 = res.getQuantityString(line1ResId, num, num, formattedTime); 723 line2 = res.getString(R.string.zen_mode_until, formattedTime); 724 } else if (minutes < DAY_MINUTES) { 725 // display as hours 726 num = Math.round(minutes / 60f); 727 int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short 728 : R.plurals.zen_mode_duration_hours_summary; 729 summary = res.getQuantityString(summaryResId, num, num, formattedTime); 730 int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short 731 : R.plurals.zen_mode_duration_hours; 732 line1 = res.getQuantityString(line1ResId, num, num, formattedTime); 733 line2 = res.getString(R.string.zen_mode_until, formattedTime); 734 } else { 735 // display as day/time 736 summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime); 737 } 738 final Uri id = toCountdownConditionId(time); 739 return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE, 740 Condition.FLAG_RELEVANT_NOW); 741 } 742 toNextAlarmCondition(Context context, long now, long alarm, int userHandle)743 public static Condition toNextAlarmCondition(Context context, long now, long alarm, 744 int userHandle) { 745 final CharSequence formattedTime = getFormattedTime(context, alarm, userHandle); 746 final Resources res = context.getResources(); 747 final String line1 = res.getString(R.string.zen_mode_alarm, formattedTime); 748 final Uri id = toCountdownConditionId(alarm); 749 return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE, 750 Condition.FLAG_RELEVANT_NOW); 751 } 752 getFormattedTime(Context context, long time, int userHandle)753 private static CharSequence getFormattedTime(Context context, long time, int userHandle) { 754 String skeleton = "EEE " + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"); 755 GregorianCalendar now = new GregorianCalendar(); 756 GregorianCalendar endTime = new GregorianCalendar(); 757 endTime.setTimeInMillis(time); 758 if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR) 759 && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH) 760 && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) { 761 skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"; 762 } 763 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 764 return DateFormat.format(pattern, time); 765 } 766 767 // ==== Built-in system conditions ==== 768 769 public static final String SYSTEM_AUTHORITY = "android"; 770 771 // ==== Built-in system condition: countdown ==== 772 773 public static final String COUNTDOWN_PATH = "countdown"; 774 toCountdownConditionId(long time)775 public static Uri toCountdownConditionId(long time) { 776 return new Uri.Builder().scheme(Condition.SCHEME) 777 .authority(SYSTEM_AUTHORITY) 778 .appendPath(COUNTDOWN_PATH) 779 .appendPath(Long.toString(time)) 780 .build(); 781 } 782 tryParseCountdownConditionId(Uri conditionId)783 public static long tryParseCountdownConditionId(Uri conditionId) { 784 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; 785 if (conditionId.getPathSegments().size() != 2 786 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; 787 try { 788 return Long.parseLong(conditionId.getPathSegments().get(1)); 789 } catch (RuntimeException e) { 790 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e); 791 return 0; 792 } 793 } 794 isValidCountdownConditionId(Uri conditionId)795 public static boolean isValidCountdownConditionId(Uri conditionId) { 796 return tryParseCountdownConditionId(conditionId) != 0; 797 } 798 799 // ==== Built-in system condition: schedule ==== 800 801 public static final String SCHEDULE_PATH = "schedule"; 802 toScheduleConditionId(ScheduleInfo schedule)803 public static Uri toScheduleConditionId(ScheduleInfo schedule) { 804 return new Uri.Builder().scheme(Condition.SCHEME) 805 .authority(SYSTEM_AUTHORITY) 806 .appendPath(SCHEDULE_PATH) 807 .appendQueryParameter("days", toDayList(schedule.days)) 808 .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute) 809 .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute) 810 .appendQueryParameter("exitAtAlarm", String.valueOf(schedule.exitAtAlarm)) 811 .build(); 812 } 813 isValidScheduleConditionId(Uri conditionId)814 public static boolean isValidScheduleConditionId(Uri conditionId) { 815 return tryParseScheduleConditionId(conditionId) != null; 816 } 817 tryParseScheduleConditionId(Uri conditionId)818 public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { 819 final boolean isSchedule = conditionId != null 820 && conditionId.getScheme().equals(Condition.SCHEME) 821 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) 822 && conditionId.getPathSegments().size() == 1 823 && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH); 824 if (!isSchedule) return null; 825 final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); 826 final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); 827 if (start == null || end == null) return null; 828 final ScheduleInfo rt = new ScheduleInfo(); 829 rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\."); 830 rt.startHour = start[0]; 831 rt.startMinute = start[1]; 832 rt.endHour = end[0]; 833 rt.endMinute = end[1]; 834 rt.exitAtAlarm = safeBoolean(conditionId.getQueryParameter("exitAtAlarm"), false); 835 return rt; 836 } 837 getScheduleConditionProvider()838 public static ComponentName getScheduleConditionProvider() { 839 return new ComponentName(SYSTEM_AUTHORITY, "ScheduleConditionProvider"); 840 } 841 842 public static class ScheduleInfo { 843 public int[] days; 844 public int startHour; 845 public int startMinute; 846 public int endHour; 847 public int endMinute; 848 public boolean exitAtAlarm; 849 public long nextAlarm; 850 851 @Override hashCode()852 public int hashCode() { 853 return 0; 854 } 855 856 @Override equals(Object o)857 public boolean equals(Object o) { 858 if (!(o instanceof ScheduleInfo)) return false; 859 final ScheduleInfo other = (ScheduleInfo) o; 860 return toDayList(days).equals(toDayList(other.days)) 861 && startHour == other.startHour 862 && startMinute == other.startMinute 863 && endHour == other.endHour 864 && endMinute == other.endMinute 865 && exitAtAlarm == other.exitAtAlarm; 866 } 867 copy()868 public ScheduleInfo copy() { 869 final ScheduleInfo rt = new ScheduleInfo(); 870 if (days != null) { 871 rt.days = new int[days.length]; 872 System.arraycopy(days, 0, rt.days, 0, days.length); 873 } 874 rt.startHour = startHour; 875 rt.startMinute = startMinute; 876 rt.endHour = endHour; 877 rt.endMinute = endMinute; 878 rt.exitAtAlarm = exitAtAlarm; 879 rt.nextAlarm = nextAlarm; 880 return rt; 881 } 882 883 @Override toString()884 public String toString() { 885 return "ScheduleInfo{" + 886 "days=" + Arrays.toString(days) + 887 ", startHour=" + startHour + 888 ", startMinute=" + startMinute + 889 ", endHour=" + endHour + 890 ", endMinute=" + endMinute + 891 ", exitAtAlarm=" + exitAtAlarm + 892 ", nextAlarm=" + nextAlarm + 893 '}'; 894 } 895 } 896 897 // ==== Built-in system condition: event ==== 898 899 public static final String EVENT_PATH = "event"; 900 toEventConditionId(EventInfo event)901 public static Uri toEventConditionId(EventInfo event) { 902 return new Uri.Builder().scheme(Condition.SCHEME) 903 .authority(SYSTEM_AUTHORITY) 904 .appendPath(EVENT_PATH) 905 .appendQueryParameter("userId", Long.toString(event.userId)) 906 .appendQueryParameter("calendar", event.calendar != null ? event.calendar : "") 907 .appendQueryParameter("reply", Integer.toString(event.reply)) 908 .build(); 909 } 910 isValidEventConditionId(Uri conditionId)911 public static boolean isValidEventConditionId(Uri conditionId) { 912 return tryParseEventConditionId(conditionId) != null; 913 } 914 tryParseEventConditionId(Uri conditionId)915 public static EventInfo tryParseEventConditionId(Uri conditionId) { 916 final boolean isEvent = conditionId != null 917 && conditionId.getScheme().equals(Condition.SCHEME) 918 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) 919 && conditionId.getPathSegments().size() == 1 920 && conditionId.getPathSegments().get(0).equals(EVENT_PATH); 921 if (!isEvent) return null; 922 final EventInfo rt = new EventInfo(); 923 rt.userId = tryParseInt(conditionId.getQueryParameter("userId"), UserHandle.USER_NULL); 924 rt.calendar = conditionId.getQueryParameter("calendar"); 925 if (TextUtils.isEmpty(rt.calendar) || tryParseLong(rt.calendar, -1L) != -1L) { 926 rt.calendar = null; 927 } 928 rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); 929 return rt; 930 } 931 getEventConditionProvider()932 public static ComponentName getEventConditionProvider() { 933 return new ComponentName(SYSTEM_AUTHORITY, "EventConditionProvider"); 934 } 935 936 public static class EventInfo { 937 public static final int REPLY_ANY_EXCEPT_NO = 0; 938 public static final int REPLY_YES_OR_MAYBE = 1; 939 public static final int REPLY_YES = 2; 940 941 public int userId = UserHandle.USER_NULL; // USER_NULL = unspecified - use current user 942 public String calendar; // CalendarContract.Calendars.OWNER_ACCOUNT, or null for any 943 public int reply; 944 945 @Override hashCode()946 public int hashCode() { 947 return 0; 948 } 949 950 @Override equals(Object o)951 public boolean equals(Object o) { 952 if (!(o instanceof EventInfo)) return false; 953 final EventInfo other = (EventInfo) o; 954 return userId == other.userId 955 && Objects.equals(calendar, other.calendar) 956 && reply == other.reply; 957 } 958 copy()959 public EventInfo copy() { 960 final EventInfo rt = new EventInfo(); 961 rt.userId = userId; 962 rt.calendar = calendar; 963 rt.reply = reply; 964 return rt; 965 } 966 resolveUserId(int userId)967 public static int resolveUserId(int userId) { 968 return userId == UserHandle.USER_NULL ? ActivityManager.getCurrentUser() : userId; 969 } 970 } 971 972 // ==== End built-in system conditions ==== 973 tryParseHourAndMinute(String value)974 private static int[] tryParseHourAndMinute(String value) { 975 if (TextUtils.isEmpty(value)) return null; 976 final int i = value.indexOf('.'); 977 if (i < 1 || i >= value.length() - 1) return null; 978 final int hour = tryParseInt(value.substring(0, i), -1); 979 final int minute = tryParseInt(value.substring(i + 1), -1); 980 return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; 981 } 982 tryParseZenMode(String value, int defValue)983 private static int tryParseZenMode(String value, int defValue) { 984 final int rt = tryParseInt(value, defValue); 985 return Global.isValidZenMode(rt) ? rt : defValue; 986 } 987 newRuleId()988 public static String newRuleId() { 989 return UUID.randomUUID().toString().replace("-", ""); 990 } 991 getConditionSummary(Context context, ZenModeConfig config, int userHandle, boolean shortVersion)992 public static String getConditionSummary(Context context, ZenModeConfig config, 993 int userHandle, boolean shortVersion) { 994 return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion); 995 } 996 getConditionLine(Context context, ZenModeConfig config, int userHandle, boolean useLine1, boolean shortVersion)997 private static String getConditionLine(Context context, ZenModeConfig config, 998 int userHandle, boolean useLine1, boolean shortVersion) { 999 if (config == null) return ""; 1000 if (config.manualRule != null) { 1001 final Uri id = config.manualRule.conditionId; 1002 if (id == null) { 1003 return context.getString(com.android.internal.R.string.zen_mode_forever); 1004 } 1005 final long time = tryParseCountdownConditionId(id); 1006 Condition c = config.manualRule.condition; 1007 if (time > 0) { 1008 final long now = System.currentTimeMillis(); 1009 final long span = time - now; 1010 c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS), 1011 userHandle, shortVersion); 1012 } 1013 final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary; 1014 return TextUtils.isEmpty(rt) ? "" : rt; 1015 } 1016 String summary = ""; 1017 for (ZenRule automaticRule : config.automaticRules.values()) { 1018 if (automaticRule.isAutomaticActive()) { 1019 if (summary.isEmpty()) { 1020 summary = automaticRule.name; 1021 } else { 1022 summary = context.getResources() 1023 .getString(R.string.zen_mode_rule_name_combination, summary, 1024 automaticRule.name); 1025 } 1026 } 1027 } 1028 return summary; 1029 } 1030 1031 public static class ZenRule implements Parcelable { 1032 public boolean enabled; 1033 public boolean snoozing; // user manually disabled this instance 1034 public String name; // required for automatic 1035 public int zenMode; 1036 public Uri conditionId; // required for automatic 1037 public Condition condition; // optional 1038 public ComponentName component; // optional 1039 public String id; // required for automatic (unique) 1040 public long creationTime; // required for automatic 1041 ZenRule()1042 public ZenRule() { } 1043 ZenRule(Parcel source)1044 public ZenRule(Parcel source) { 1045 enabled = source.readInt() == 1; 1046 snoozing = source.readInt() == 1; 1047 if (source.readInt() == 1) { 1048 name = source.readString(); 1049 } 1050 zenMode = source.readInt(); 1051 conditionId = source.readParcelable(null); 1052 condition = source.readParcelable(null); 1053 component = source.readParcelable(null); 1054 if (source.readInt() == 1) { 1055 id = source.readString(); 1056 } 1057 creationTime = source.readLong(); 1058 } 1059 1060 @Override describeContents()1061 public int describeContents() { 1062 return 0; 1063 } 1064 1065 @Override writeToParcel(Parcel dest, int flags)1066 public void writeToParcel(Parcel dest, int flags) { 1067 dest.writeInt(enabled ? 1 : 0); 1068 dest.writeInt(snoozing ? 1 : 0); 1069 if (name != null) { 1070 dest.writeInt(1); 1071 dest.writeString(name); 1072 } else { 1073 dest.writeInt(0); 1074 } 1075 dest.writeInt(zenMode); 1076 dest.writeParcelable(conditionId, 0); 1077 dest.writeParcelable(condition, 0); 1078 dest.writeParcelable(component, 0); 1079 if (id != null) { 1080 dest.writeInt(1); 1081 dest.writeString(id); 1082 } else { 1083 dest.writeInt(0); 1084 } 1085 dest.writeLong(creationTime); 1086 } 1087 1088 @Override toString()1089 public String toString() { 1090 return new StringBuilder(ZenRule.class.getSimpleName()).append('[') 1091 .append("enabled=").append(enabled) 1092 .append(",snoozing=").append(snoozing) 1093 .append(",name=").append(name) 1094 .append(",zenMode=").append(Global.zenModeToString(zenMode)) 1095 .append(",conditionId=").append(conditionId) 1096 .append(",condition=").append(condition) 1097 .append(",component=").append(component) 1098 .append(",id=").append(id) 1099 .append(",creationTime=").append(creationTime) 1100 .append(']').toString(); 1101 } 1102 appendDiff(Diff d, String item, ZenRule from, ZenRule to)1103 private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) { 1104 if (d == null) return; 1105 if (from == null) { 1106 if (to != null) { 1107 d.addLine(item, "insert"); 1108 } 1109 return; 1110 } 1111 from.appendDiff(d, item, to); 1112 } 1113 appendDiff(Diff d, String item, ZenRule to)1114 private void appendDiff(Diff d, String item, ZenRule to) { 1115 if (to == null) { 1116 d.addLine(item, "delete"); 1117 return; 1118 } 1119 if (enabled != to.enabled) { 1120 d.addLine(item, "enabled", enabled, to.enabled); 1121 } 1122 if (snoozing != to.snoozing) { 1123 d.addLine(item, "snoozing", snoozing, to.snoozing); 1124 } 1125 if (!Objects.equals(name, to.name)) { 1126 d.addLine(item, "name", name, to.name); 1127 } 1128 if (zenMode != to.zenMode) { 1129 d.addLine(item, "zenMode", zenMode, to.zenMode); 1130 } 1131 if (!Objects.equals(conditionId, to.conditionId)) { 1132 d.addLine(item, "conditionId", conditionId, to.conditionId); 1133 } 1134 if (!Objects.equals(condition, to.condition)) { 1135 d.addLine(item, "condition", condition, to.condition); 1136 } 1137 if (!Objects.equals(component, to.component)) { 1138 d.addLine(item, "component", component, to.component); 1139 } 1140 if (!Objects.equals(id, to.id)) { 1141 d.addLine(item, "id", id, to.id); 1142 } 1143 if (creationTime != to.creationTime) { 1144 d.addLine(item, "creationTime", creationTime, to.creationTime); 1145 } 1146 } 1147 1148 @Override equals(Object o)1149 public boolean equals(Object o) { 1150 if (!(o instanceof ZenRule)) return false; 1151 if (o == this) return true; 1152 final ZenRule other = (ZenRule) o; 1153 return other.enabled == enabled 1154 && other.snoozing == snoozing 1155 && Objects.equals(other.name, name) 1156 && other.zenMode == zenMode 1157 && Objects.equals(other.conditionId, conditionId) 1158 && Objects.equals(other.condition, condition) 1159 && Objects.equals(other.component, component) 1160 && Objects.equals(other.id, id) 1161 && other.creationTime == creationTime; 1162 } 1163 1164 @Override hashCode()1165 public int hashCode() { 1166 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, 1167 component, id, creationTime); 1168 } 1169 isAutomaticActive()1170 public boolean isAutomaticActive() { 1171 return enabled && !snoozing && component != null && isTrueOrUnknown(); 1172 } 1173 isTrueOrUnknown()1174 public boolean isTrueOrUnknown() { 1175 return condition != null && (condition.state == Condition.STATE_TRUE 1176 || condition.state == Condition.STATE_UNKNOWN); 1177 } 1178 1179 public static final Parcelable.Creator<ZenRule> CREATOR 1180 = new Parcelable.Creator<ZenRule>() { 1181 @Override 1182 public ZenRule createFromParcel(Parcel source) { 1183 return new ZenRule(source); 1184 } 1185 @Override 1186 public ZenRule[] newArray(int size) { 1187 return new ZenRule[size]; 1188 } 1189 }; 1190 } 1191 1192 // Legacy config 1193 public static final class XmlV1 { 1194 public static final String SLEEP_MODE_NIGHTS = "nights"; 1195 public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; 1196 public static final String SLEEP_MODE_DAYS_PREFIX = "days:"; 1197 1198 private static final String EXIT_CONDITION_TAG = "exitCondition"; 1199 private static final String EXIT_CONDITION_ATT_COMPONENT = "component"; 1200 private static final String SLEEP_TAG = "sleep"; 1201 private static final String SLEEP_ATT_MODE = "mode"; 1202 private static final String SLEEP_ATT_NONE = "none"; 1203 1204 private static final String SLEEP_ATT_START_HR = "startHour"; 1205 private static final String SLEEP_ATT_START_MIN = "startMin"; 1206 private static final String SLEEP_ATT_END_HR = "endHour"; 1207 private static final String SLEEP_ATT_END_MIN = "endMin"; 1208 1209 public boolean allowCalls; 1210 public boolean allowMessages; 1211 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 1212 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 1213 public int allowFrom = SOURCE_ANYONE; 1214 1215 public String sleepMode; // nights, weeknights, days:1,2,3 Calendar.days 1216 public int sleepStartHour; // 0-23 1217 public int sleepStartMinute; // 0-59 1218 public int sleepEndHour; 1219 public int sleepEndMinute; 1220 public boolean sleepNone; // false = priority, true = none 1221 public ComponentName[] conditionComponents; 1222 public Uri[] conditionIds; 1223 public Condition exitCondition; // manual exit condition 1224 public ComponentName exitConditionComponent; // manual exit condition component 1225 isValidSleepMode(String sleepMode)1226 private static boolean isValidSleepMode(String sleepMode) { 1227 return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS) 1228 || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null; 1229 } 1230 tryParseDays(String sleepMode)1231 public static int[] tryParseDays(String sleepMode) { 1232 if (sleepMode == null) return null; 1233 sleepMode = sleepMode.trim(); 1234 if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS; 1235 if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS; 1236 if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null; 1237 if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null; 1238 return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ","); 1239 } 1240 readXml(XmlPullParser parser)1241 public static XmlV1 readXml(XmlPullParser parser) 1242 throws XmlPullParserException, IOException { 1243 int type; 1244 String tag; 1245 XmlV1 rt = new XmlV1(); 1246 final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>(); 1247 final ArrayList<Uri> conditionIds = new ArrayList<Uri>(); 1248 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1249 tag = parser.getName(); 1250 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 1251 if (!conditionComponents.isEmpty()) { 1252 rt.conditionComponents = conditionComponents 1253 .toArray(new ComponentName[conditionComponents.size()]); 1254 rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]); 1255 } 1256 return rt; 1257 } 1258 if (type == XmlPullParser.START_TAG) { 1259 if (ALLOW_TAG.equals(tag)) { 1260 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); 1261 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); 1262 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 1263 DEFAULT_ALLOW_REMINDERS); 1264 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, 1265 DEFAULT_ALLOW_EVENTS); 1266 rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); 1267 if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { 1268 throw new IndexOutOfBoundsException("bad source in config:" 1269 + rt.allowFrom); 1270 } 1271 } else if (SLEEP_TAG.equals(tag)) { 1272 final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); 1273 rt.sleepMode = isValidSleepMode(mode)? mode : null; 1274 rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false); 1275 final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0); 1276 final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0); 1277 final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0); 1278 final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0); 1279 rt.sleepStartHour = isValidHour(startHour) ? startHour : 0; 1280 rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0; 1281 rt.sleepEndHour = isValidHour(endHour) ? endHour : 0; 1282 rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0; 1283 } else if (CONDITION_TAG.equals(tag)) { 1284 final ComponentName component = 1285 safeComponentName(parser, CONDITION_ATT_COMPONENT); 1286 final Uri conditionId = safeUri(parser, CONDITION_ATT_ID); 1287 if (component != null && conditionId != null) { 1288 conditionComponents.add(component); 1289 conditionIds.add(conditionId); 1290 } 1291 } else if (EXIT_CONDITION_TAG.equals(tag)) { 1292 rt.exitCondition = readConditionXml(parser); 1293 if (rt.exitCondition != null) { 1294 rt.exitConditionComponent = 1295 safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); 1296 } 1297 } 1298 } 1299 } 1300 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 1301 } 1302 } 1303 1304 public interface Migration { migrate(XmlV1 v1)1305 ZenModeConfig migrate(XmlV1 v1); 1306 } 1307 1308 public static class Diff { 1309 private final ArrayList<String> lines = new ArrayList<>(); 1310 1311 @Override toString()1312 public String toString() { 1313 final StringBuilder sb = new StringBuilder("Diff["); 1314 final int N = lines.size(); 1315 for (int i = 0; i < N; i++) { 1316 if (i > 0) { 1317 sb.append(','); 1318 } 1319 sb.append(lines.get(i)); 1320 } 1321 return sb.append(']').toString(); 1322 } 1323 addLine(String item, String action)1324 private Diff addLine(String item, String action) { 1325 lines.add(item + ":" + action); 1326 return this; 1327 } 1328 addLine(String item, String subitem, Object from, Object to)1329 public Diff addLine(String item, String subitem, Object from, Object to) { 1330 return addLine(item + "." + subitem, from, to); 1331 } 1332 addLine(String item, Object from, Object to)1333 public Diff addLine(String item, Object from, Object to) { 1334 return addLine(item, from + "->" + to); 1335 } 1336 } 1337 1338 } 1339