ZenModeConfig.java revision 1b8b22b1a412539020f78a132cff7c8fa7fae258
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 17package android.service.notification; 18 19import android.app.ActivityManager; 20import android.app.NotificationManager.Policy; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.res.Resources; 24import android.net.Uri; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.os.UserHandle; 28import android.provider.Settings.Global; 29import android.text.TextUtils; 30import android.text.format.DateFormat; 31import android.util.ArrayMap; 32import android.util.ArraySet; 33import android.util.Slog; 34 35import com.android.internal.R; 36 37import org.xmlpull.v1.XmlPullParser; 38import org.xmlpull.v1.XmlPullParserException; 39import org.xmlpull.v1.XmlSerializer; 40 41import java.io.IOException; 42import java.util.ArrayList; 43import java.util.Calendar; 44import java.util.Locale; 45import java.util.Objects; 46import java.util.UUID; 47 48/** 49 * Persisted configuration for zen mode. 50 * 51 * @hide 52 */ 53public class ZenModeConfig implements Parcelable { 54 private static String TAG = "ZenModeConfig"; 55 56 public static final int SOURCE_ANYONE = 0; 57 public static final int SOURCE_CONTACT = 1; 58 public static final int SOURCE_STAR = 2; 59 public static final int MAX_SOURCE = SOURCE_STAR; 60 private static final int DEFAULT_SOURCE = SOURCE_CONTACT; 61 62 public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 63 Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; 64 public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 65 Calendar.WEDNESDAY, Calendar.THURSDAY }; 66 public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY }; 67 68 public static final int[] MINUTE_BUCKETS = generateMinuteBuckets(); 69 private static final int SECONDS_MS = 1000; 70 private static final int MINUTES_MS = 60 * SECONDS_MS; 71 private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; 72 73 private static final boolean DEFAULT_ALLOW_CALLS = true; 74 private static final boolean DEFAULT_ALLOW_MESSAGES = false; 75 private static final boolean DEFAULT_ALLOW_REMINDERS = true; 76 private static final boolean DEFAULT_ALLOW_EVENTS = true; 77 private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; 78 79 private static final int XML_VERSION = 2; 80 private static final String ZEN_TAG = "zen"; 81 private static final String ZEN_ATT_VERSION = "version"; 82 private static final String ALLOW_TAG = "allow"; 83 private static final String ALLOW_ATT_CALLS = "calls"; 84 private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; 85 private static final String ALLOW_ATT_MESSAGES = "messages"; 86 private static final String ALLOW_ATT_FROM = "from"; 87 private static final String ALLOW_ATT_CALLS_FROM = "callsFrom"; 88 private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom"; 89 private static final String ALLOW_ATT_REMINDERS = "reminders"; 90 private static final String ALLOW_ATT_EVENTS = "events"; 91 92 private static final String CONDITION_TAG = "condition"; 93 private static final String CONDITION_ATT_COMPONENT = "component"; 94 private static final String CONDITION_ATT_ID = "id"; 95 private static final String CONDITION_ATT_SUMMARY = "summary"; 96 private static final String CONDITION_ATT_LINE1 = "line1"; 97 private static final String CONDITION_ATT_LINE2 = "line2"; 98 private static final String CONDITION_ATT_ICON = "icon"; 99 private static final String CONDITION_ATT_STATE = "state"; 100 private static final String CONDITION_ATT_FLAGS = "flags"; 101 102 private static final String MANUAL_TAG = "manual"; 103 private static final String AUTOMATIC_TAG = "automatic"; 104 105 private static final String RULE_ATT_ID = "ruleId"; 106 private static final String RULE_ATT_ENABLED = "enabled"; 107 private static final String RULE_ATT_SNOOZING = "snoozing"; 108 private static final String RULE_ATT_NAME = "name"; 109 private static final String RULE_ATT_COMPONENT = "component"; 110 private static final String RULE_ATT_ZEN = "zen"; 111 private static final String RULE_ATT_CONDITION_ID = "conditionId"; 112 113 public boolean allowCalls = DEFAULT_ALLOW_CALLS; 114 public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; 115 public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; 116 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 117 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 118 public int allowCallsFrom = DEFAULT_SOURCE; 119 public int allowMessagesFrom = DEFAULT_SOURCE; 120 121 public ZenRule manualRule; 122 public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); 123 124 public ZenModeConfig() { } 125 126 public ZenModeConfig(Parcel source) { 127 allowCalls = source.readInt() == 1; 128 allowRepeatCallers = source.readInt() == 1; 129 allowMessages = source.readInt() == 1; 130 allowReminders = source.readInt() == 1; 131 allowEvents = source.readInt() == 1; 132 allowCallsFrom = source.readInt(); 133 allowMessagesFrom = source.readInt(); 134 manualRule = source.readParcelable(null); 135 final int len = source.readInt(); 136 if (len > 0) { 137 final String[] ids = new String[len]; 138 final ZenRule[] rules = new ZenRule[len]; 139 source.readStringArray(ids); 140 source.readTypedArray(rules, ZenRule.CREATOR); 141 for (int i = 0; i < len; i++) { 142 automaticRules.put(ids[i], rules[i]); 143 } 144 } 145 } 146 147 @Override 148 public void writeToParcel(Parcel dest, int flags) { 149 dest.writeInt(allowCalls ? 1 : 0); 150 dest.writeInt(allowRepeatCallers ? 1 : 0); 151 dest.writeInt(allowMessages ? 1 : 0); 152 dest.writeInt(allowReminders ? 1 : 0); 153 dest.writeInt(allowEvents ? 1 : 0); 154 dest.writeInt(allowCallsFrom); 155 dest.writeInt(allowMessagesFrom); 156 dest.writeParcelable(manualRule, 0); 157 if (!automaticRules.isEmpty()) { 158 final int len = automaticRules.size(); 159 final String[] ids = new String[len]; 160 final ZenRule[] rules = new ZenRule[len]; 161 for (int i = 0; i < len; i++) { 162 ids[i] = automaticRules.keyAt(i); 163 rules[i] = automaticRules.valueAt(i); 164 } 165 dest.writeInt(len); 166 dest.writeStringArray(ids); 167 dest.writeTypedArray(rules, 0); 168 } else { 169 dest.writeInt(0); 170 } 171 } 172 173 @Override 174 public String toString() { 175 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') 176 .append("allowCalls=").append(allowCalls) 177 .append(",allowRepeatCallers=").append(allowRepeatCallers) 178 .append(",allowMessages=").append(allowMessages) 179 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) 180 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) 181 .append(",allowReminders=").append(allowReminders) 182 .append(",allowEvents=").append(allowEvents) 183 .append(",automaticRules=").append(automaticRules) 184 .append(",manualRule=").append(manualRule) 185 .append(']').toString(); 186 } 187 188 public boolean isValid() { 189 if (!isValidManualRule(manualRule)) return false; 190 final int N = automaticRules.size(); 191 for (int i = 0; i < N; i++) { 192 if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false; 193 } 194 return true; 195 } 196 197 private static boolean isValidManualRule(ZenRule rule) { 198 return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule); 199 } 200 201 private static boolean isValidAutomaticRule(ZenRule rule) { 202 return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode) 203 && rule.conditionId != null && sameCondition(rule); 204 } 205 206 private static boolean sameCondition(ZenRule rule) { 207 if (rule == null) return false; 208 if (rule.conditionId == null) { 209 return rule.condition == null; 210 } else { 211 return rule.condition == null || rule.conditionId.equals(rule.condition.id); 212 } 213 } 214 215 private static int[] generateMinuteBuckets() { 216 final int maxHrs = 12; 217 final int[] buckets = new int[maxHrs + 3]; 218 buckets[0] = 15; 219 buckets[1] = 30; 220 buckets[2] = 45; 221 for (int i = 1; i <= maxHrs; i++) { 222 buckets[2 + i] = 60 * i; 223 } 224 return buckets; 225 } 226 227 public static String sourceToString(int source) { 228 switch (source) { 229 case SOURCE_ANYONE: 230 return "anyone"; 231 case SOURCE_CONTACT: 232 return "contacts"; 233 case SOURCE_STAR: 234 return "stars"; 235 default: 236 return "UNKNOWN"; 237 } 238 } 239 240 @Override 241 public boolean equals(Object o) { 242 if (!(o instanceof ZenModeConfig)) return false; 243 if (o == this) return true; 244 final ZenModeConfig other = (ZenModeConfig) o; 245 return other.allowCalls == allowCalls 246 && other.allowRepeatCallers == allowRepeatCallers 247 && other.allowMessages == allowMessages 248 && other.allowCallsFrom == allowCallsFrom 249 && other.allowMessagesFrom == allowMessagesFrom 250 && other.allowReminders == allowReminders 251 && other.allowEvents == allowEvents 252 && Objects.equals(other.automaticRules, automaticRules) 253 && Objects.equals(other.manualRule, manualRule); 254 } 255 256 @Override 257 public int hashCode() { 258 return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, 259 allowMessagesFrom, allowReminders, allowEvents, automaticRules, manualRule); 260 } 261 262 private static String toDayList(int[] days) { 263 if (days == null || days.length == 0) return ""; 264 final StringBuilder sb = new StringBuilder(); 265 for (int i = 0; i < days.length; i++) { 266 if (i > 0) sb.append('.'); 267 sb.append(days[i]); 268 } 269 return sb.toString(); 270 } 271 272 private static int[] tryParseDayList(String dayList, String sep) { 273 if (dayList == null) return null; 274 final String[] tokens = dayList.split(sep); 275 if (tokens.length == 0) return null; 276 final int[] rt = new int[tokens.length]; 277 for (int i = 0; i < tokens.length; i++) { 278 final int day = tryParseInt(tokens[i], -1); 279 if (day == -1) return null; 280 rt[i] = day; 281 } 282 return rt; 283 } 284 285 private static int tryParseInt(String value, int defValue) { 286 if (TextUtils.isEmpty(value)) return defValue; 287 try { 288 return Integer.valueOf(value); 289 } catch (NumberFormatException e) { 290 return defValue; 291 } 292 } 293 294 private static long tryParseLong(String value, long defValue) { 295 if (TextUtils.isEmpty(value)) return defValue; 296 try { 297 return Long.valueOf(value); 298 } catch (NumberFormatException e) { 299 return defValue; 300 } 301 } 302 303 public static ZenModeConfig readXml(XmlPullParser parser, Migration migration) 304 throws XmlPullParserException, IOException { 305 int type = parser.getEventType(); 306 if (type != XmlPullParser.START_TAG) return null; 307 String tag = parser.getName(); 308 if (!ZEN_TAG.equals(tag)) return null; 309 final ZenModeConfig rt = new ZenModeConfig(); 310 final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION); 311 if (version == 1) { 312 final XmlV1 v1 = XmlV1.readXml(parser); 313 return migration.migrate(v1); 314 } 315 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 316 tag = parser.getName(); 317 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 318 return rt; 319 } 320 if (type == XmlPullParser.START_TAG) { 321 if (ALLOW_TAG.equals(tag)) { 322 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); 323 rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS, 324 DEFAULT_ALLOW_REPEAT_CALLERS); 325 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); 326 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 327 DEFAULT_ALLOW_REMINDERS); 328 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); 329 final int from = safeInt(parser, ALLOW_ATT_FROM, -1); 330 final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); 331 final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); 332 if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { 333 rt.allowCallsFrom = callsFrom; 334 rt.allowMessagesFrom = messagesFrom; 335 } else if (isValidSource(from)) { 336 Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from)); 337 rt.allowCallsFrom = from; 338 rt.allowMessagesFrom = from; 339 } else { 340 rt.allowCallsFrom = DEFAULT_SOURCE; 341 rt.allowMessagesFrom = DEFAULT_SOURCE; 342 } 343 } else if (MANUAL_TAG.equals(tag)) { 344 rt.manualRule = readRuleXml(parser, false /*conditionRequired*/); 345 } else if (AUTOMATIC_TAG.equals(tag)) { 346 final String id = parser.getAttributeValue(null, RULE_ATT_ID); 347 final ZenRule automaticRule = readRuleXml(parser, true /*conditionRequired*/); 348 if (id != null && automaticRule != null) { 349 rt.automaticRules.put(id, automaticRule); 350 } 351 } 352 } 353 } 354 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 355 } 356 357 public void writeXml(XmlSerializer out) throws IOException { 358 out.startTag(null, ZEN_TAG); 359 out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION)); 360 361 out.startTag(null, ALLOW_TAG); 362 out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); 363 out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers)); 364 out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); 365 out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); 366 out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); 367 out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom)); 368 out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom)); 369 out.endTag(null, ALLOW_TAG); 370 371 if (manualRule != null) { 372 out.startTag(null, MANUAL_TAG); 373 writeRuleXml(manualRule, out); 374 out.endTag(null, MANUAL_TAG); 375 } 376 final int N = automaticRules.size(); 377 for (int i = 0; i < N; i++) { 378 final String id = automaticRules.keyAt(i); 379 final ZenRule automaticRule = automaticRules.valueAt(i); 380 out.startTag(null, AUTOMATIC_TAG); 381 out.attribute(null, RULE_ATT_ID, id); 382 writeRuleXml(automaticRule, out); 383 out.endTag(null, AUTOMATIC_TAG); 384 } 385 out.endTag(null, ZEN_TAG); 386 } 387 388 public static ZenRule readRuleXml(XmlPullParser parser, boolean conditionRequired) { 389 final ZenRule rt = new ZenRule(); 390 rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true); 391 rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false); 392 rt.name = parser.getAttributeValue(null, RULE_ATT_NAME); 393 final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN); 394 rt.zenMode = tryParseZenMode(zen, -1); 395 if (rt.zenMode == -1) { 396 Slog.w(TAG, "Bad zen mode in rule xml:" + zen); 397 return null; 398 } 399 rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID); 400 rt.component = safeComponentName(parser, RULE_ATT_COMPONENT); 401 rt.condition = readConditionXml(parser); 402 return rt; 403 } 404 405 public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException { 406 out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled)); 407 out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing)); 408 if (rule.name != null) { 409 out.attribute(null, RULE_ATT_NAME, rule.name); 410 } 411 out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode)); 412 if (rule.component != null) { 413 out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString()); 414 } 415 if (rule.conditionId != null) { 416 out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString()); 417 } 418 if (rule.condition != null) { 419 writeConditionXml(rule.condition, out); 420 } 421 } 422 423 public static Condition readConditionXml(XmlPullParser parser) { 424 final Uri id = safeUri(parser, CONDITION_ATT_ID); 425 if (id == null) return null; 426 final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); 427 final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); 428 final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); 429 final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); 430 final int state = safeInt(parser, CONDITION_ATT_STATE, -1); 431 final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); 432 try { 433 return new Condition(id, summary, line1, line2, icon, state, flags); 434 } catch (IllegalArgumentException e) { 435 Slog.w(TAG, "Unable to read condition xml", e); 436 return null; 437 } 438 } 439 440 public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException { 441 out.attribute(null, CONDITION_ATT_ID, c.id.toString()); 442 out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); 443 out.attribute(null, CONDITION_ATT_LINE1, c.line1); 444 out.attribute(null, CONDITION_ATT_LINE2, c.line2); 445 out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon)); 446 out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state)); 447 out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags)); 448 } 449 450 public static boolean isValidHour(int val) { 451 return val >= 0 && val < 24; 452 } 453 454 public static boolean isValidMinute(int val) { 455 return val >= 0 && val < 60; 456 } 457 458 private static boolean isValidSource(int source) { 459 return source >= SOURCE_ANYONE && source <= MAX_SOURCE; 460 } 461 462 private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { 463 final String val = parser.getAttributeValue(null, att); 464 if (TextUtils.isEmpty(val)) return defValue; 465 return Boolean.valueOf(val); 466 } 467 468 private static int safeInt(XmlPullParser parser, String att, int defValue) { 469 final String val = parser.getAttributeValue(null, att); 470 return tryParseInt(val, defValue); 471 } 472 473 private static ComponentName safeComponentName(XmlPullParser parser, String att) { 474 final String val = parser.getAttributeValue(null, att); 475 if (TextUtils.isEmpty(val)) return null; 476 return ComponentName.unflattenFromString(val); 477 } 478 479 private static Uri safeUri(XmlPullParser parser, String att) { 480 final String val = parser.getAttributeValue(null, att); 481 if (TextUtils.isEmpty(val)) return null; 482 return Uri.parse(val); 483 } 484 485 public ArraySet<String> getAutomaticRuleNames() { 486 final ArraySet<String> rt = new ArraySet<String>(); 487 for (int i = 0; i < automaticRules.size(); i++) { 488 rt.add(automaticRules.valueAt(i).name); 489 } 490 return rt; 491 } 492 493 @Override 494 public int describeContents() { 495 return 0; 496 } 497 498 public ZenModeConfig copy() { 499 final Parcel parcel = Parcel.obtain(); 500 try { 501 writeToParcel(parcel, 0); 502 parcel.setDataPosition(0); 503 return new ZenModeConfig(parcel); 504 } finally { 505 parcel.recycle(); 506 } 507 } 508 509 public static final Parcelable.Creator<ZenModeConfig> CREATOR 510 = new Parcelable.Creator<ZenModeConfig>() { 511 @Override 512 public ZenModeConfig createFromParcel(Parcel source) { 513 return new ZenModeConfig(source); 514 } 515 516 @Override 517 public ZenModeConfig[] newArray(int size) { 518 return new ZenModeConfig[size]; 519 } 520 }; 521 522 public Policy toNotificationPolicy() { 523 int priorityCategories = 0; 524 int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; 525 int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; 526 if (allowCalls) { 527 priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; 528 } 529 if (allowMessages) { 530 priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; 531 } 532 if (allowEvents) { 533 priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; 534 } 535 if (allowReminders) { 536 priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; 537 } 538 if (allowRepeatCallers) { 539 priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; 540 } 541 priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); 542 priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); 543 return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders); 544 } 545 546 private static int sourceToPrioritySenders(int source, int def) { 547 switch (source) { 548 case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY; 549 case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS; 550 case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED; 551 default: return def; 552 } 553 } 554 555 private static int prioritySendersToSource(int prioritySenders, int def) { 556 switch (prioritySenders) { 557 case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; 558 case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; 559 case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; 560 default: return def; 561 } 562 } 563 564 public void applyNotificationPolicy(Policy policy) { 565 if (policy == null) return; 566 allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0; 567 allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; 568 allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; 569 allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; 570 allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) 571 != 0; 572 allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom); 573 allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders, 574 allowMessagesFrom); 575 } 576 577 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { 578 return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/); 579 } 580 581 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle, 582 boolean shortVersion) { 583 final long now = System.currentTimeMillis(); 584 final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; 585 return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle, 586 shortVersion); 587 } 588 589 public static Condition toTimeCondition(Context context, long time, int minutes, long now, 590 int userHandle, boolean shortVersion) { 591 final int num, summaryResId, line1ResId; 592 if (minutes < 60) { 593 // display as minutes 594 num = minutes; 595 summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short 596 : R.plurals.zen_mode_duration_minutes_summary; 597 line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short 598 : R.plurals.zen_mode_duration_minutes; 599 } else { 600 // display as hours 601 num = Math.round(minutes / 60f); 602 summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short 603 : R.plurals.zen_mode_duration_hours_summary; 604 line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short 605 : R.plurals.zen_mode_duration_hours; 606 } 607 final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"; 608 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 609 final CharSequence formattedTime = DateFormat.format(pattern, time); 610 final Resources res = context.getResources(); 611 final String summary = res.getQuantityString(summaryResId, num, num, formattedTime); 612 final String line1 = res.getQuantityString(line1ResId, num, num, formattedTime); 613 final String line2 = res.getString(R.string.zen_mode_until, formattedTime); 614 final Uri id = toCountdownConditionId(time); 615 return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE, 616 Condition.FLAG_RELEVANT_NOW); 617 } 618 619 // ==== Built-in system conditions ==== 620 621 public static final String SYSTEM_AUTHORITY = "android"; 622 623 // ==== Built-in system condition: countdown ==== 624 625 public static final String COUNTDOWN_PATH = "countdown"; 626 627 public static Uri toCountdownConditionId(long time) { 628 return new Uri.Builder().scheme(Condition.SCHEME) 629 .authority(SYSTEM_AUTHORITY) 630 .appendPath(COUNTDOWN_PATH) 631 .appendPath(Long.toString(time)) 632 .build(); 633 } 634 635 public static long tryParseCountdownConditionId(Uri conditionId) { 636 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; 637 if (conditionId.getPathSegments().size() != 2 638 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; 639 try { 640 return Long.parseLong(conditionId.getPathSegments().get(1)); 641 } catch (RuntimeException e) { 642 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e); 643 return 0; 644 } 645 } 646 647 public static boolean isValidCountdownConditionId(Uri conditionId) { 648 return tryParseCountdownConditionId(conditionId) != 0; 649 } 650 651 // ==== Built-in system condition: schedule ==== 652 653 public static final String SCHEDULE_PATH = "schedule"; 654 655 public static Uri toScheduleConditionId(ScheduleInfo schedule) { 656 return new Uri.Builder().scheme(Condition.SCHEME) 657 .authority(SYSTEM_AUTHORITY) 658 .appendPath(SCHEDULE_PATH) 659 .appendQueryParameter("days", toDayList(schedule.days)) 660 .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute) 661 .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute) 662 .build(); 663 } 664 665 public static boolean isValidScheduleConditionId(Uri conditionId) { 666 return tryParseScheduleConditionId(conditionId) != null; 667 } 668 669 public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) { 670 final boolean isSchedule = conditionId != null 671 && conditionId.getScheme().equals(Condition.SCHEME) 672 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) 673 && conditionId.getPathSegments().size() == 1 674 && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH); 675 if (!isSchedule) return null; 676 final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); 677 final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); 678 if (start == null || end == null) return null; 679 final ScheduleInfo rt = new ScheduleInfo(); 680 rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\."); 681 rt.startHour = start[0]; 682 rt.startMinute = start[1]; 683 rt.endHour = end[0]; 684 rt.endMinute = end[1]; 685 return rt; 686 } 687 688 public static class ScheduleInfo { 689 public int[] days; 690 public int startHour; 691 public int startMinute; 692 public int endHour; 693 public int endMinute; 694 695 @Override 696 public int hashCode() { 697 return 0; 698 } 699 700 @Override 701 public boolean equals(Object o) { 702 if (!(o instanceof ScheduleInfo)) return false; 703 final ScheduleInfo other = (ScheduleInfo) o; 704 return toDayList(days).equals(toDayList(other.days)) 705 && startHour == other.startHour 706 && startMinute == other.startMinute 707 && endHour == other.endHour 708 && endMinute == other.endMinute; 709 } 710 711 public ScheduleInfo copy() { 712 final ScheduleInfo rt = new ScheduleInfo(); 713 if (days != null) { 714 rt.days = new int[days.length]; 715 System.arraycopy(days, 0, rt.days, 0, days.length); 716 } 717 rt.startHour = startHour; 718 rt.startMinute = startMinute; 719 rt.endHour = endHour; 720 rt.endMinute = endMinute; 721 return rt; 722 } 723 } 724 725 // ==== Built-in system condition: event ==== 726 727 public static final String EVENT_PATH = "event"; 728 729 public static Uri toEventConditionId(EventInfo event) { 730 return new Uri.Builder().scheme(Condition.SCHEME) 731 .authority(SYSTEM_AUTHORITY) 732 .appendPath(EVENT_PATH) 733 .appendQueryParameter("userId", Long.toString(event.userId)) 734 .appendQueryParameter("calendar", Long.toString(event.calendar)) 735 .appendQueryParameter("reply", Integer.toString(event.reply)) 736 .build(); 737 } 738 739 public static boolean isValidEventConditionId(Uri conditionId) { 740 return tryParseEventConditionId(conditionId) != null; 741 } 742 743 public static EventInfo tryParseEventConditionId(Uri conditionId) { 744 final boolean isEvent = conditionId != null 745 && conditionId.getScheme().equals(Condition.SCHEME) 746 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) 747 && conditionId.getPathSegments().size() == 1 748 && conditionId.getPathSegments().get(0).equals(EVENT_PATH); 749 if (!isEvent) return null; 750 final EventInfo rt = new EventInfo(); 751 rt.userId = tryParseInt(conditionId.getQueryParameter("userId"), UserHandle.USER_NULL); 752 rt.calendar = tryParseLong(conditionId.getQueryParameter("calendar"), 753 EventInfo.ANY_CALENDAR); 754 rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); 755 return rt; 756 } 757 758 public static class EventInfo { 759 public static final long ANY_CALENDAR = 0; 760 761 public static final int REPLY_ANY_EXCEPT_NO = 0; 762 public static final int REPLY_YES_OR_MAYBE = 1; 763 public static final int REPLY_YES = 2; 764 765 public int userId = UserHandle.USER_NULL; // USER_NULL = unspecified - use current user 766 public long calendar = ANY_CALENDAR; // CalendarContract.Calendars._ID, or ANY_CALENDAR 767 public int reply; 768 769 @Override 770 public int hashCode() { 771 return 0; 772 } 773 774 @Override 775 public boolean equals(Object o) { 776 if (!(o instanceof EventInfo)) return false; 777 final EventInfo other = (EventInfo) o; 778 return userId == other.userId 779 && calendar == other.calendar 780 && reply == other.reply; 781 } 782 783 public EventInfo copy() { 784 final EventInfo rt = new EventInfo(); 785 rt.userId = userId; 786 rt.calendar = calendar; 787 rt.reply = reply; 788 return rt; 789 } 790 791 public static int resolveUserId(int userId) { 792 return userId == UserHandle.USER_NULL ? ActivityManager.getCurrentUser() : userId; 793 794 } 795 } 796 797 // ==== End built-in system conditions ==== 798 799 private static int[] tryParseHourAndMinute(String value) { 800 if (TextUtils.isEmpty(value)) return null; 801 final int i = value.indexOf('.'); 802 if (i < 1 || i >= value.length() - 1) return null; 803 final int hour = tryParseInt(value.substring(0, i), -1); 804 final int minute = tryParseInt(value.substring(i + 1), -1); 805 return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; 806 } 807 808 private static int tryParseZenMode(String value, int defValue) { 809 final int rt = tryParseInt(value, defValue); 810 return Global.isValidZenMode(rt) ? rt : defValue; 811 } 812 813 public String newRuleId() { 814 return UUID.randomUUID().toString().replace("-", ""); 815 } 816 817 public static String getConditionLine1(Context context, ZenModeConfig config, 818 int userHandle, boolean shortVersion) { 819 return getConditionLine(context, config, userHandle, true /*useLine1*/, shortVersion); 820 } 821 822 public static String getConditionSummary(Context context, ZenModeConfig config, 823 int userHandle, boolean shortVersion) { 824 return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion); 825 } 826 827 private static String getConditionLine(Context context, ZenModeConfig config, 828 int userHandle, boolean useLine1, boolean shortVersion) { 829 if (config == null) return ""; 830 if (config.manualRule != null) { 831 final Uri id = config.manualRule.conditionId; 832 if (id == null) { 833 return context.getString(com.android.internal.R.string.zen_mode_forever); 834 } 835 final long time = tryParseCountdownConditionId(id); 836 Condition c = config.manualRule.condition; 837 if (time > 0) { 838 final long now = System.currentTimeMillis(); 839 final long span = time - now; 840 c = toTimeCondition(context, 841 time, Math.round(span / (float) MINUTES_MS), now, userHandle, shortVersion); 842 } 843 final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary; 844 return TextUtils.isEmpty(rt) ? "" : rt; 845 } 846 String summary = ""; 847 for (ZenRule automaticRule : config.automaticRules.values()) { 848 if (automaticRule.isAutomaticActive()) { 849 if (summary.isEmpty()) { 850 summary = automaticRule.name; 851 } else { 852 summary = context.getResources() 853 .getString(R.string.zen_mode_rule_name_combination, summary, 854 automaticRule.name); 855 } 856 } 857 } 858 return summary; 859 } 860 861 public static class ZenRule implements Parcelable { 862 public boolean enabled; 863 public boolean snoozing; // user manually disabled this instance 864 public String name; // required for automatic (unique) 865 public int zenMode; 866 public Uri conditionId; // required for automatic 867 public Condition condition; // optional 868 public ComponentName component; // optional 869 870 public ZenRule() { } 871 872 public ZenRule(Parcel source) { 873 enabled = source.readInt() == 1; 874 snoozing = source.readInt() == 1; 875 if (source.readInt() == 1) { 876 name = source.readString(); 877 } 878 zenMode = source.readInt(); 879 conditionId = source.readParcelable(null); 880 condition = source.readParcelable(null); 881 component = source.readParcelable(null); 882 } 883 884 @Override 885 public int describeContents() { 886 return 0; 887 } 888 889 @Override 890 public void writeToParcel(Parcel dest, int flags) { 891 dest.writeInt(enabled ? 1 : 0); 892 dest.writeInt(snoozing ? 1 : 0); 893 if (name != null) { 894 dest.writeInt(1); 895 dest.writeString(name); 896 } else { 897 dest.writeInt(0); 898 } 899 dest.writeInt(zenMode); 900 dest.writeParcelable(conditionId, 0); 901 dest.writeParcelable(condition, 0); 902 dest.writeParcelable(component, 0); 903 } 904 905 @Override 906 public String toString() { 907 return new StringBuilder(ZenRule.class.getSimpleName()).append('[') 908 .append("enabled=").append(enabled) 909 .append(",snoozing=").append(snoozing) 910 .append(",name=").append(name) 911 .append(",zenMode=").append(Global.zenModeToString(zenMode)) 912 .append(",conditionId=").append(conditionId) 913 .append(",condition=").append(condition) 914 .append(",component=").append(component) 915 .append(']').toString(); 916 } 917 918 @Override 919 public boolean equals(Object o) { 920 if (!(o instanceof ZenRule)) return false; 921 if (o == this) return true; 922 final ZenRule other = (ZenRule) o; 923 return other.enabled == enabled 924 && other.snoozing == snoozing 925 && Objects.equals(other.name, name) 926 && other.zenMode == zenMode 927 && Objects.equals(other.conditionId, conditionId) 928 && Objects.equals(other.condition, condition) 929 && Objects.equals(other.component, component); 930 } 931 932 @Override 933 public int hashCode() { 934 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, 935 component); 936 } 937 938 public boolean isAutomaticActive() { 939 return enabled && !snoozing && component != null && isTrueOrUnknown(); 940 } 941 942 public boolean isTrueOrUnknown() { 943 return condition != null && (condition.state == Condition.STATE_TRUE 944 || condition.state == Condition.STATE_UNKNOWN); 945 } 946 947 public static final Parcelable.Creator<ZenRule> CREATOR 948 = new Parcelable.Creator<ZenRule>() { 949 @Override 950 public ZenRule createFromParcel(Parcel source) { 951 return new ZenRule(source); 952 } 953 @Override 954 public ZenRule[] newArray(int size) { 955 return new ZenRule[size]; 956 } 957 }; 958 } 959 960 // Legacy config 961 public static final class XmlV1 { 962 public static final String SLEEP_MODE_NIGHTS = "nights"; 963 public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; 964 public static final String SLEEP_MODE_DAYS_PREFIX = "days:"; 965 966 private static final String EXIT_CONDITION_TAG = "exitCondition"; 967 private static final String EXIT_CONDITION_ATT_COMPONENT = "component"; 968 private static final String SLEEP_TAG = "sleep"; 969 private static final String SLEEP_ATT_MODE = "mode"; 970 private static final String SLEEP_ATT_NONE = "none"; 971 972 private static final String SLEEP_ATT_START_HR = "startHour"; 973 private static final String SLEEP_ATT_START_MIN = "startMin"; 974 private static final String SLEEP_ATT_END_HR = "endHour"; 975 private static final String SLEEP_ATT_END_MIN = "endMin"; 976 977 public boolean allowCalls; 978 public boolean allowMessages; 979 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 980 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 981 public int allowFrom = SOURCE_ANYONE; 982 983 public String sleepMode; // nights, weeknights, days:1,2,3 Calendar.days 984 public int sleepStartHour; // 0-23 985 public int sleepStartMinute; // 0-59 986 public int sleepEndHour; 987 public int sleepEndMinute; 988 public boolean sleepNone; // false = priority, true = none 989 public ComponentName[] conditionComponents; 990 public Uri[] conditionIds; 991 public Condition exitCondition; // manual exit condition 992 public ComponentName exitConditionComponent; // manual exit condition component 993 994 private static boolean isValidSleepMode(String sleepMode) { 995 return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS) 996 || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null; 997 } 998 999 public static int[] tryParseDays(String sleepMode) { 1000 if (sleepMode == null) return null; 1001 sleepMode = sleepMode.trim(); 1002 if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS; 1003 if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS; 1004 if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null; 1005 if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null; 1006 return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ","); 1007 } 1008 1009 public static XmlV1 readXml(XmlPullParser parser) 1010 throws XmlPullParserException, IOException { 1011 int type; 1012 String tag; 1013 XmlV1 rt = new XmlV1(); 1014 final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>(); 1015 final ArrayList<Uri> conditionIds = new ArrayList<Uri>(); 1016 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1017 tag = parser.getName(); 1018 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 1019 if (!conditionComponents.isEmpty()) { 1020 rt.conditionComponents = conditionComponents 1021 .toArray(new ComponentName[conditionComponents.size()]); 1022 rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]); 1023 } 1024 return rt; 1025 } 1026 if (type == XmlPullParser.START_TAG) { 1027 if (ALLOW_TAG.equals(tag)) { 1028 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); 1029 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); 1030 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 1031 DEFAULT_ALLOW_REMINDERS); 1032 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, 1033 DEFAULT_ALLOW_EVENTS); 1034 rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); 1035 if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { 1036 throw new IndexOutOfBoundsException("bad source in config:" 1037 + rt.allowFrom); 1038 } 1039 } else if (SLEEP_TAG.equals(tag)) { 1040 final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); 1041 rt.sleepMode = isValidSleepMode(mode)? mode : null; 1042 rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false); 1043 final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0); 1044 final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0); 1045 final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0); 1046 final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0); 1047 rt.sleepStartHour = isValidHour(startHour) ? startHour : 0; 1048 rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0; 1049 rt.sleepEndHour = isValidHour(endHour) ? endHour : 0; 1050 rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0; 1051 } else if (CONDITION_TAG.equals(tag)) { 1052 final ComponentName component = 1053 safeComponentName(parser, CONDITION_ATT_COMPONENT); 1054 final Uri conditionId = safeUri(parser, CONDITION_ATT_ID); 1055 if (component != null && conditionId != null) { 1056 conditionComponents.add(component); 1057 conditionIds.add(conditionId); 1058 } 1059 } else if (EXIT_CONDITION_TAG.equals(tag)) { 1060 rt.exitCondition = readConditionXml(parser); 1061 if (rt.exitCondition != null) { 1062 rt.exitConditionComponent = 1063 safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); 1064 } 1065 } 1066 } 1067 } 1068 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 1069 } 1070 } 1071 1072 public interface Migration { 1073 ZenModeConfig migrate(XmlV1 v1); 1074 } 1075 1076} 1077