2 * Copyright (c) 2014, The Android Open Source Project
4 * Licensed under the Apache License, 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.service.notification;
19 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
20 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
21 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
23 import android.app.ActivityManager;
24 import android.app.AlarmManager;
25 import android.app.NotificationManager;
26 import android.app.NotificationManager.Policy;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.res.Resources;
32 import android.net.Uri;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.os.UserHandle;
36 import android.provider.Settings.Global;
37 import android.text.TextUtils;
38 import android.text.format.DateFormat;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Slog;
42 import android.util.proto.ProtoOutputStream;
44 import com.android.internal.R;
46 import org.xmlpull.v1.XmlPullParser;
47 import org.xmlpull.v1.XmlPullParserException;
48 import org.xmlpull.v1.XmlSerializer;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Calendar;
54 import java.util.Date;
55 import java.util.GregorianCalendar;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Objects;
59 import java.util.TimeZone;
60 import java.util.UUID;
63 * Persisted configuration for zen mode.
67 public class ZenModeConfig implements Parcelable {
68 private static String TAG = "ZenModeConfig";
70 public static final int SOURCE_ANYONE = 0;
71 public static final int SOURCE_CONTACT = 1;
72 public static final int SOURCE_STAR = 2;
73 public static final int MAX_SOURCE = SOURCE_STAR;
74 private static final int DEFAULT_SOURCE = SOURCE_CONTACT;
75 private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
77 public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
78 public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
79 public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
80 EVENTS_DEFAULT_RULE_ID);
82 public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
83 Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
85 public static final int[] MINUTE_BUCKETS = generateMinuteBuckets();
86 private static final int SECONDS_MS = 1000;
87 private static final int MINUTES_MS = 60 * SECONDS_MS;
88 private static final int DAY_MINUTES = 24 * 60;
89 private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
91 // Default allow categories set in readXml() from default_zen_mode_config.xml,
92 // fallback/upgrade values:
93 private static final boolean DEFAULT_ALLOW_ALARMS = true;
94 private static final boolean DEFAULT_ALLOW_MEDIA = true;
95 private static final boolean DEFAULT_ALLOW_SYSTEM = false;
96 private static final boolean DEFAULT_ALLOW_CALLS = true;
97 private static final boolean DEFAULT_ALLOW_MESSAGES = false;
98 private static final boolean DEFAULT_ALLOW_REMINDERS = false;
99 private static final boolean DEFAULT_ALLOW_EVENTS = false;
100 private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true;
101 private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
102 private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 0;
104 public static final int XML_VERSION = 8;
105 public static final String ZEN_TAG = "zen";
106 private static final String ZEN_ATT_VERSION = "version";
107 private static final String ZEN_ATT_USER = "user";
108 private static final String ALLOW_TAG = "allow";
109 private static final String ALLOW_ATT_ALARMS = "alarms";
110 private static final String ALLOW_ATT_MEDIA = "media";
111 private static final String ALLOW_ATT_SYSTEM = "system";
112 private static final String ALLOW_ATT_CALLS = "calls";
113 private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
114 private static final String ALLOW_ATT_MESSAGES = "messages";
115 private static final String ALLOW_ATT_FROM = "from";
116 private static final String ALLOW_ATT_CALLS_FROM = "callsFrom";
117 private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom";
118 private static final String ALLOW_ATT_REMINDERS = "reminders";
119 private static final String ALLOW_ATT_EVENTS = "events";
120 private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
121 private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
122 private static final String DISALLOW_TAG = "disallow";
123 private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
124 private static final String STATE_TAG = "state";
125 private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
127 private static final String CONDITION_ATT_ID = "id";
128 private static final String CONDITION_ATT_SUMMARY = "summary";
129 private static final String CONDITION_ATT_LINE1 = "line1";
130 private static final String CONDITION_ATT_LINE2 = "line2";
131 private static final String CONDITION_ATT_ICON = "icon";
132 private static final String CONDITION_ATT_STATE = "state";
133 private static final String CONDITION_ATT_FLAGS = "flags";
135 private static final String MANUAL_TAG = "manual";
136 private static final String AUTOMATIC_TAG = "automatic";
138 private static final String RULE_ATT_ID = "ruleId";
139 private static final String RULE_ATT_ENABLED = "enabled";
140 private static final String RULE_ATT_SNOOZING = "snoozing";
141 private static final String RULE_ATT_NAME = "name";
142 private static final String RULE_ATT_COMPONENT = "component";
143 private static final String RULE_ATT_ZEN = "zen";
144 private static final String RULE_ATT_CONDITION_ID = "conditionId";
145 private static final String RULE_ATT_CREATION_TIME = "creationTime";
146 private static final String RULE_ATT_ENABLER = "enabler";
148 public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
149 public boolean allowMedia = DEFAULT_ALLOW_MEDIA;
150 public boolean allowSystem = DEFAULT_ALLOW_SYSTEM;
151 public boolean allowCalls = DEFAULT_ALLOW_CALLS;
152 public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
153 public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
154 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
155 public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
156 public int allowCallsFrom = DEFAULT_CALLS_SOURCE;
157 public int allowMessagesFrom = DEFAULT_SOURCE;
158 public int user = UserHandle.USER_SYSTEM;
159 public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
160 public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
163 public ZenRule manualRule;
164 public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
166 public ZenModeConfig() { }
168 public ZenModeConfig(Parcel source) {
169 allowCalls = source.readInt() == 1;
170 allowRepeatCallers = source.readInt() == 1;
171 allowMessages = source.readInt() == 1;
172 allowReminders = source.readInt() == 1;
173 allowEvents = source.readInt() == 1;
174 allowCallsFrom = source.readInt();
175 allowMessagesFrom = source.readInt();
176 user = source.readInt();
177 manualRule = source.readParcelable(null);
178 final int len = source.readInt();
180 final String[] ids = new String[len];
181 final ZenRule[] rules = new ZenRule[len];
182 source.readStringArray(ids);
183 source.readTypedArray(rules, ZenRule.CREATOR);
184 for (int i = 0; i < len; i++) {
185 automaticRules.put(ids[i], rules[i]);
188 allowAlarms = source.readInt() == 1;
189 allowMedia = source.readInt() == 1;
190 allowSystem = source.readInt() == 1;
191 suppressedVisualEffects = source.readInt();
192 areChannelsBypassingDnd = source.readInt() == 1;
196 public void writeToParcel(Parcel dest, int flags) {
197 dest.writeInt(allowCalls ? 1 : 0);
198 dest.writeInt(allowRepeatCallers ? 1 : 0);
199 dest.writeInt(allowMessages ? 1 : 0);
200 dest.writeInt(allowReminders ? 1 : 0);
201 dest.writeInt(allowEvents ? 1 : 0);
202 dest.writeInt(allowCallsFrom);
203 dest.writeInt(allowMessagesFrom);
205 dest.writeParcelable(manualRule, 0);
206 if (!automaticRules.isEmpty()) {
207 final int len = automaticRules.size();
208 final String[] ids = new String[len];
209 final ZenRule[] rules = new ZenRule[len];
210 for (int i = 0; i < len; i++) {
211 ids[i] = automaticRules.keyAt(i);
212 rules[i] = automaticRules.valueAt(i);
215 dest.writeStringArray(ids);
216 dest.writeTypedArray(rules, 0);
220 dest.writeInt(allowAlarms ? 1 : 0);
221 dest.writeInt(allowMedia ? 1 : 0);
222 dest.writeInt(allowSystem ? 1 : 0);
223 dest.writeInt(suppressedVisualEffects);
224 dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
228 public String toString() {
229 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
230 .append("user=").append(user)
231 .append(",allowAlarms=").append(allowAlarms)
232 .append(",allowMedia=").append(allowMedia)
233 .append(",allowSystem=").append(allowSystem)
234 .append(",allowReminders=").append(allowReminders)
235 .append(",allowEvents=").append(allowEvents)
236 .append(",allowCalls=").append(allowCalls)
237 .append(",allowRepeatCallers=").append(allowRepeatCallers)
238 .append(",allowMessages=").append(allowMessages)
239 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
240 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
241 .append(",suppressedVisualEffects=").append(suppressedVisualEffects)
242 .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd)
243 .append(",automaticRules=").append(automaticRules)
244 .append(",manualRule=").append(manualRule)
245 .append(']').toString();
248 private Diff diff(ZenModeConfig to) {
249 final Diff d = new Diff();
251 return d.addLine("config", "delete");
253 if (user != to.user) {
254 d.addLine("user", user, to.user);
256 if (allowAlarms != to.allowAlarms) {
257 d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
259 if (allowMedia != to.allowMedia) {
260 d.addLine("allowMedia", allowMedia, to.allowMedia);
262 if (allowSystem != to.allowSystem) {
263 d.addLine("allowSystem", allowSystem, to.allowSystem);
265 if (allowCalls != to.allowCalls) {
266 d.addLine("allowCalls", allowCalls, to.allowCalls);
268 if (allowReminders != to.allowReminders) {
269 d.addLine("allowReminders", allowReminders, to.allowReminders);
271 if (allowEvents != to.allowEvents) {
272 d.addLine("allowEvents", allowEvents, to.allowEvents);
274 if (allowRepeatCallers != to.allowRepeatCallers) {
275 d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
277 if (allowMessages != to.allowMessages) {
278 d.addLine("allowMessages", allowMessages, to.allowMessages);
280 if (allowCallsFrom != to.allowCallsFrom) {
281 d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
283 if (allowMessagesFrom != to.allowMessagesFrom) {
284 d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
286 if (suppressedVisualEffects != to.suppressedVisualEffects) {
287 d.addLine("suppressedVisualEffects", suppressedVisualEffects,
288 to.suppressedVisualEffects);
290 final ArraySet<String> allRules = new ArraySet<>();
291 addKeys(allRules, automaticRules);
292 addKeys(allRules, to.automaticRules);
293 final int N = allRules.size();
294 for (int i = 0; i < N; i++) {
295 final String rule = allRules.valueAt(i);
296 final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
297 final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
298 ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
300 ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
302 if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
303 d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
304 to.areChannelsBypassingDnd);
309 public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
311 final Diff d = new Diff();
313 d.addLine("config", "insert");
317 return from.diff(to);
320 private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
322 for (int i = 0; i < map.size(); i++) {
323 set.add(map.keyAt(i));
328 public boolean isValid() {
329 if (!isValidManualRule(manualRule)) return false;
330 final int N = automaticRules.size();
331 for (int i = 0; i < N; i++) {
332 if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
337 private static boolean isValidManualRule(ZenRule rule) {
338 return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
341 private static boolean isValidAutomaticRule(ZenRule rule) {
342 return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
343 && rule.conditionId != null && sameCondition(rule);
346 private static boolean sameCondition(ZenRule rule) {
347 if (rule == null) return false;
348 if (rule.conditionId == null) {
349 return rule.condition == null;
351 return rule.condition == null || rule.conditionId.equals(rule.condition.id);
355 private static int[] generateMinuteBuckets() {
356 final int maxHrs = 12;
357 final int[] buckets = new int[maxHrs + 3];
361 for (int i = 1; i <= maxHrs; i++) {
362 buckets[2 + i] = 60 * i;
367 public static String sourceToString(int source) {
381 public boolean equals(Object o) {
382 if (!(o instanceof ZenModeConfig)) return false;
383 if (o == this) return true;
384 final ZenModeConfig other = (ZenModeConfig) o;
385 return other.allowAlarms == allowAlarms
386 && other.allowMedia == allowMedia
387 && other.allowSystem == allowSystem
388 && other.allowCalls == allowCalls
389 && other.allowRepeatCallers == allowRepeatCallers
390 && other.allowMessages == allowMessages
391 && other.allowCallsFrom == allowCallsFrom
392 && other.allowMessagesFrom == allowMessagesFrom
393 && other.allowReminders == allowReminders
394 && other.allowEvents == allowEvents
395 && other.user == user
396 && Objects.equals(other.automaticRules, automaticRules)
397 && Objects.equals(other.manualRule, manualRule)
398 && other.suppressedVisualEffects == suppressedVisualEffects
399 && other.areChannelsBypassingDnd == areChannelsBypassingDnd;
403 public int hashCode() {
404 return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
405 allowRepeatCallers, allowMessages,
406 allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
407 user, automaticRules, manualRule,
408 suppressedVisualEffects, areChannelsBypassingDnd);
411 private static String toDayList(int[] days) {
412 if (days == null || days.length == 0) return "";
413 final StringBuilder sb = new StringBuilder();
414 for (int i = 0; i < days.length; i++) {
415 if (i > 0) sb.append('.');
418 return sb.toString();
421 private static int[] tryParseDayList(String dayList, String sep) {
422 if (dayList == null) return null;
423 final String[] tokens = dayList.split(sep);
424 if (tokens.length == 0) return null;
425 final int[] rt = new int[tokens.length];
426 for (int i = 0; i < tokens.length; i++) {
427 final int day = tryParseInt(tokens[i], -1);
428 if (day == -1) return null;
434 private static int tryParseInt(String value, int defValue) {
435 if (TextUtils.isEmpty(value)) return defValue;
437 return Integer.parseInt(value);
438 } catch (NumberFormatException e) {
443 private static long tryParseLong(String value, long defValue) {
444 if (TextUtils.isEmpty(value)) return defValue;
446 return Long.parseLong(value);
447 } catch (NumberFormatException e) {
452 public static ZenModeConfig readXml(XmlPullParser parser)
453 throws XmlPullParserException, IOException {
454 int type = parser.getEventType();
455 if (type != XmlPullParser.START_TAG) return null;
456 String tag = parser.getName();
457 if (!ZEN_TAG.equals(tag)) return null;
458 final ZenModeConfig rt = new ZenModeConfig();
459 rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
460 rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
461 boolean readSuppressedEffects = false;
462 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
463 tag = parser.getName();
464 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
467 if (type == XmlPullParser.START_TAG) {
468 if (ALLOW_TAG.equals(tag)) {
469 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
470 DEFAULT_ALLOW_CALLS);
471 rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
472 DEFAULT_ALLOW_REPEAT_CALLERS);
473 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
474 DEFAULT_ALLOW_MESSAGES);
475 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
476 DEFAULT_ALLOW_REMINDERS);
477 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
478 final int from = safeInt(parser, ALLOW_ATT_FROM, -1);
479 final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1);
480 final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1);
481 if (isValidSource(callsFrom) && isValidSource(messagesFrom)) {
482 rt.allowCallsFrom = callsFrom;
483 rt.allowMessagesFrom = messagesFrom;
484 } else if (isValidSource(from)) {
485 Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from));
486 rt.allowCallsFrom = from;
487 rt.allowMessagesFrom = from;
489 rt.allowCallsFrom = DEFAULT_CALLS_SOURCE;
490 rt.allowMessagesFrom = DEFAULT_SOURCE;
492 rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
493 rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA,
494 DEFAULT_ALLOW_MEDIA);
495 rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM);
497 // migrate old suppressed visual effects fields, if they still exist in the xml
498 Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF);
499 if (allowWhenScreenOff != null) {
500 readSuppressedEffects = true;
501 if (!allowWhenScreenOff) {
502 rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS
503 | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
506 Boolean allowWhenScreenOn = unsafeBoolean(parser, ALLOW_ATT_SCREEN_ON);
507 if (allowWhenScreenOn != null) {
508 readSuppressedEffects = true;
509 if (!allowWhenScreenOn) {
510 rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_PEEK;
513 if (readSuppressedEffects) {
514 Slog.d(TAG, "Migrated visual effects to " + rt.suppressedVisualEffects);
516 } else if (DISALLOW_TAG.equals(tag) && !readSuppressedEffects) {
517 // only read from suppressed visual effects field if we haven't just migrated
518 // the values from allowOn/allowOff, lest we wipe out those settings
519 rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
520 DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
521 } else if (MANUAL_TAG.equals(tag)) {
522 rt.manualRule = readRuleXml(parser);
523 } else if (AUTOMATIC_TAG.equals(tag)) {
524 final String id = parser.getAttributeValue(null, RULE_ATT_ID);
525 final ZenRule automaticRule = readRuleXml(parser);
526 if (id != null && automaticRule != null) {
527 automaticRule.id = id;
528 rt.automaticRules.put(id, automaticRule);
530 } else if (STATE_TAG.equals(tag)) {
531 rt.areChannelsBypassingDnd = safeBoolean(parser,
532 STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
536 throw new IllegalStateException("Failed to reach END_DOCUMENT");
540 * Writes XML of current ZenModeConfig
541 * @param out serializer
542 * @param version uses XML_VERSION if version is null
543 * @throws IOException
545 public void writeXml(XmlSerializer out, Integer version) throws IOException {
546 out.startTag(null, ZEN_TAG);
547 out.attribute(null, ZEN_ATT_VERSION, version == null
548 ? Integer.toString(XML_VERSION) : Integer.toString(version));
549 out.attribute(null, ZEN_ATT_USER, Integer.toString(user));
550 out.startTag(null, ALLOW_TAG);
551 out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
552 out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers));
553 out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
554 out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
555 out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
556 out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom));
557 out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
558 out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
559 out.attribute(null, ALLOW_ATT_MEDIA, Boolean.toString(allowMedia));
560 out.attribute(null, ALLOW_ATT_SYSTEM, Boolean.toString(allowSystem));
561 out.endTag(null, ALLOW_TAG);
563 out.startTag(null, DISALLOW_TAG);
564 out.attribute(null, DISALLOW_ATT_VISUAL_EFFECTS, Integer.toString(suppressedVisualEffects));
565 out.endTag(null, DISALLOW_TAG);
567 if (manualRule != null) {
568 out.startTag(null, MANUAL_TAG);
569 writeRuleXml(manualRule, out);
570 out.endTag(null, MANUAL_TAG);
572 final int N = automaticRules.size();
573 for (int i = 0; i < N; i++) {
574 final String id = automaticRules.keyAt(i);
575 final ZenRule automaticRule = automaticRules.valueAt(i);
576 out.startTag(null, AUTOMATIC_TAG);
577 out.attribute(null, RULE_ATT_ID, id);
578 writeRuleXml(automaticRule, out);
579 out.endTag(null, AUTOMATIC_TAG);
582 out.startTag(null, STATE_TAG);
583 out.attribute(null, STATE_ATT_CHANNELS_BYPASSING_DND,
584 Boolean.toString(areChannelsBypassingDnd));
585 out.endTag(null, STATE_TAG);
587 out.endTag(null, ZEN_TAG);
590 public static ZenRule readRuleXml(XmlPullParser parser) {
591 final ZenRule rt = new ZenRule();
592 rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
593 rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
594 rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
595 final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
596 rt.zenMode = tryParseZenMode(zen, -1);
597 if (rt.zenMode == -1) {
598 Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
601 rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
602 rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
603 rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
604 rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
605 rt.condition = readConditionXml(parser);
607 // all default rules and user created rules updated to zenMode important interruptions
608 if (rt.zenMode != Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
609 && Condition.isValidId(rt.conditionId, SYSTEM_AUTHORITY)) {
610 Slog.i(TAG, "Updating zenMode of automatic rule " + rt.name);
611 rt.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
616 public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
617 out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
618 out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
619 if (rule.name != null) {
620 out.attribute(null, RULE_ATT_NAME, rule.name);
622 out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
623 if (rule.component != null) {
624 out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
626 if (rule.conditionId != null) {
627 out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
629 out.attribute(null, RULE_ATT_CREATION_TIME, Long.toString(rule.creationTime));
630 if (rule.enabler != null) {
631 out.attribute(null, RULE_ATT_ENABLER, rule.enabler);
633 if (rule.condition != null) {
634 writeConditionXml(rule.condition, out);
638 public static Condition readConditionXml(XmlPullParser parser) {
639 final Uri id = safeUri(parser, CONDITION_ATT_ID);
640 if (id == null) return null;
641 final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
642 final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
643 final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
644 final int icon = safeInt(parser, CONDITION_ATT_ICON, -1);
645 final int state = safeInt(parser, CONDITION_ATT_STATE, -1);
646 final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1);
648 return new Condition(id, summary, line1, line2, icon, state, flags);
649 } catch (IllegalArgumentException e) {
650 Slog.w(TAG, "Unable to read condition xml", e);
655 public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException {
656 out.attribute(null, CONDITION_ATT_ID, c.id.toString());
657 out.attribute(null, CONDITION_ATT_SUMMARY, c.summary);
658 out.attribute(null, CONDITION_ATT_LINE1, c.line1);
659 out.attribute(null, CONDITION_ATT_LINE2, c.line2);
660 out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon));
661 out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state));
662 out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags));
665 public static boolean isValidHour(int val) {
666 return val >= 0 && val < 24;
669 public static boolean isValidMinute(int val) {
670 return val >= 0 && val < 60;
673 private static boolean isValidSource(int source) {
674 return source >= SOURCE_ANYONE && source <= MAX_SOURCE;
677 private static Boolean unsafeBoolean(XmlPullParser parser, String att) {
678 final String val = parser.getAttributeValue(null, att);
679 if (TextUtils.isEmpty(val)) return null;
680 return Boolean.parseBoolean(val);
683 private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) {
684 final String val = parser.getAttributeValue(null, att);
685 return safeBoolean(val, defValue);
688 private static boolean safeBoolean(String val, boolean defValue) {
689 if (TextUtils.isEmpty(val)) return defValue;
690 return Boolean.parseBoolean(val);
693 private static int safeInt(XmlPullParser parser, String att, int defValue) {
694 final String val = parser.getAttributeValue(null, att);
695 return tryParseInt(val, defValue);
698 private static ComponentName safeComponentName(XmlPullParser parser, String att) {
699 final String val = parser.getAttributeValue(null, att);
700 if (TextUtils.isEmpty(val)) return null;
701 return ComponentName.unflattenFromString(val);
704 private static Uri safeUri(XmlPullParser parser, String att) {
705 final String val = parser.getAttributeValue(null, att);
706 if (TextUtils.isEmpty(val)) return null;
707 return Uri.parse(val);
710 private static long safeLong(XmlPullParser parser, String att, long defValue) {
711 final String val = parser.getAttributeValue(null, att);
712 return tryParseLong(val, defValue);
716 public int describeContents() {
720 public ZenModeConfig copy() {
721 final Parcel parcel = Parcel.obtain();
723 writeToParcel(parcel, 0);
724 parcel.setDataPosition(0);
725 return new ZenModeConfig(parcel);
731 public static final Parcelable.Creator<ZenModeConfig> CREATOR
732 = new Parcelable.Creator<ZenModeConfig>() {
734 public ZenModeConfig createFromParcel(Parcel source) {
735 return new ZenModeConfig(source);
739 public ZenModeConfig[] newArray(int size) {
740 return new ZenModeConfig[size];
744 public Policy toNotificationPolicy() {
745 int priorityCategories = 0;
746 int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
747 int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS;
749 priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
752 priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
755 priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
757 if (allowReminders) {
758 priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
760 if (allowRepeatCallers) {
761 priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
764 priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
767 priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
770 priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
772 priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
773 priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
774 return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
775 suppressedVisualEffects, areChannelsBypassingDnd
776 ? Policy.STATE_CHANNELS_BYPASSING_DND : 0);
780 * Creates scheduleCalendar from a condition id
782 * @return ScheduleCalendar with info populated with conditionId
784 public static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
785 final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
786 if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
787 final ScheduleCalendar sc = new ScheduleCalendar();
788 sc.setSchedule(schedule);
789 sc.setTimeZone(TimeZone.getDefault());
793 private static int sourceToPrioritySenders(int source, int def) {
795 case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY;
796 case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS;
797 case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED;
802 private static int prioritySendersToSource(int prioritySenders, int def) {
803 switch (prioritySenders) {
804 case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
805 case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
806 case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
811 public void applyNotificationPolicy(Policy policy) {
812 if (policy == null) return;
813 allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
814 allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
815 allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
816 allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
817 allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
818 allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
819 allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
820 allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
822 allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
823 allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders,
825 if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
826 suppressedVisualEffects = policy.suppressedVisualEffects;
828 if (policy.state != Policy.STATE_UNSET) {
829 areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
833 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
834 return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/);
837 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle,
838 boolean shortVersion) {
839 final long now = System.currentTimeMillis();
840 final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
841 return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion);
844 public static Condition toTimeCondition(Context context, long time, int minutes,
845 int userHandle, boolean shortVersion) {
847 String summary, line1, line2;
848 final CharSequence formattedTime =
849 getFormattedTime(context, time, isToday(time), userHandle);
850 final Resources res = context.getResources();
852 // display as minutes
854 int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
855 : R.plurals.zen_mode_duration_minutes_summary;
856 summary = res.getQuantityString(summaryResId, num, num, formattedTime);
857 int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
858 : R.plurals.zen_mode_duration_minutes;
859 line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
860 line2 = res.getString(R.string.zen_mode_until, formattedTime);
861 } else if (minutes < DAY_MINUTES) {
863 num = Math.round(minutes / 60f);
864 int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
865 : R.plurals.zen_mode_duration_hours_summary;
866 summary = res.getQuantityString(summaryResId, num, num, formattedTime);
867 int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
868 : R.plurals.zen_mode_duration_hours;
869 line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
870 line2 = res.getString(R.string.zen_mode_until, formattedTime);
872 // display as day/time
873 summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
875 final Uri id = toCountdownConditionId(time, false);
876 return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
877 Condition.FLAG_RELEVANT_NOW);
881 * Converts countdown to alarm parameters into a condition with user facing summary
883 public static Condition toNextAlarmCondition(Context context, long alarm,
885 boolean isSameDay = isToday(alarm);
886 final CharSequence formattedTime = getFormattedTime(context, alarm, isSameDay, userHandle);
887 final Resources res = context.getResources();
888 final String line1 = res.getString(R.string.zen_mode_until, formattedTime);
889 final Uri id = toCountdownConditionId(alarm, true);
890 return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE,
891 Condition.FLAG_RELEVANT_NOW);
895 * Creates readable time from time in milliseconds
897 public static CharSequence getFormattedTime(Context context, long time, boolean isSameDay,
899 String skeleton = (!isSameDay ? "EEE " : "")
900 + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma");
901 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
902 return DateFormat.format(pattern, time);
906 * Determines whether a time in milliseconds is today or not
908 public static boolean isToday(long time) {
909 GregorianCalendar now = new GregorianCalendar();
910 GregorianCalendar endTime = new GregorianCalendar();
911 endTime.setTimeInMillis(time);
912 if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR)
913 && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH)
914 && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) {
920 // ==== Built-in system conditions ====
922 public static final String SYSTEM_AUTHORITY = "android";
924 // ==== Built-in system condition: countdown ====
926 public static final String COUNTDOWN_PATH = "countdown";
928 public static final String IS_ALARM_PATH = "alarm";
931 * Converts countdown condition parameters into a condition id.
933 public static Uri toCountdownConditionId(long time, boolean alarm) {
934 return new Uri.Builder().scheme(Condition.SCHEME)
935 .authority(SYSTEM_AUTHORITY)
936 .appendPath(COUNTDOWN_PATH)
937 .appendPath(Long.toString(time))
938 .appendPath(IS_ALARM_PATH)
939 .appendPath(Boolean.toString(alarm))
943 public static long tryParseCountdownConditionId(Uri conditionId) {
944 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0;
945 if (conditionId.getPathSegments().size() < 2
946 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0;
948 return Long.parseLong(conditionId.getPathSegments().get(1));
949 } catch (RuntimeException e) {
950 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e);
956 * Returns whether this condition is a countdown condition.
958 public static boolean isValidCountdownConditionId(Uri conditionId) {
959 return tryParseCountdownConditionId(conditionId) != 0;
963 * Returns whether this condition is a countdown to an alarm.
965 public static boolean isValidCountdownToAlarmConditionId(Uri conditionId) {
966 if (tryParseCountdownConditionId(conditionId) != 0) {
967 if (conditionId.getPathSegments().size() < 4
968 || !IS_ALARM_PATH.equals(conditionId.getPathSegments().get(2))) {
972 return Boolean.parseBoolean(conditionId.getPathSegments().get(3));
973 } catch (RuntimeException e) {
974 Slog.w(TAG, "Error parsing countdown alarm condition: " + conditionId, e);
981 // ==== Built-in system condition: schedule ====
983 public static final String SCHEDULE_PATH = "schedule";
985 public static Uri toScheduleConditionId(ScheduleInfo schedule) {
986 return new Uri.Builder().scheme(Condition.SCHEME)
987 .authority(SYSTEM_AUTHORITY)
988 .appendPath(SCHEDULE_PATH)
989 .appendQueryParameter("days", toDayList(schedule.days))
990 .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
991 .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
992 .appendQueryParameter("exitAtAlarm", String.valueOf(schedule.exitAtAlarm))
996 public static boolean isValidScheduleConditionId(Uri conditionId) {
999 info = tryParseScheduleConditionId(conditionId);
1000 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
1004 if (info == null || info.days == null || info.days.length == 0) {
1011 * Returns whether the conditionId is a valid ScheduleCondition.
1012 * If allowNever is true, this will return true even if the ScheduleCondition never occurs.
1014 public static boolean isValidScheduleConditionId(Uri conditionId, boolean allowNever) {
1017 info = tryParseScheduleConditionId(conditionId);
1018 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
1022 if (info == null || (!allowNever && (info.days == null || info.days.length == 0))) {
1028 public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
1029 final boolean isSchedule = conditionId != null
1030 && conditionId.getScheme().equals(Condition.SCHEME)
1031 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
1032 && conditionId.getPathSegments().size() == 1
1033 && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH);
1034 if (!isSchedule) return null;
1035 final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
1036 final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
1037 if (start == null || end == null) return null;
1038 final ScheduleInfo rt = new ScheduleInfo();
1039 rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
1040 rt.startHour = start[0];
1041 rt.startMinute = start[1];
1042 rt.endHour = end[0];
1043 rt.endMinute = end[1];
1044 rt.exitAtAlarm = safeBoolean(conditionId.getQueryParameter("exitAtAlarm"), false);
1048 public static ComponentName getScheduleConditionProvider() {
1049 return new ComponentName(SYSTEM_AUTHORITY, "ScheduleConditionProvider");
1052 public static class ScheduleInfo {
1054 public int startHour;
1055 public int startMinute;
1057 public int endMinute;
1058 public boolean exitAtAlarm;
1059 public long nextAlarm;
1062 public int hashCode() {
1067 public boolean equals(Object o) {
1068 if (!(o instanceof ScheduleInfo)) return false;
1069 final ScheduleInfo other = (ScheduleInfo) o;
1070 return toDayList(days).equals(toDayList(other.days))
1071 && startHour == other.startHour
1072 && startMinute == other.startMinute
1073 && endHour == other.endHour
1074 && endMinute == other.endMinute
1075 && exitAtAlarm == other.exitAtAlarm;
1078 public ScheduleInfo copy() {
1079 final ScheduleInfo rt = new ScheduleInfo();
1081 rt.days = new int[days.length];
1082 System.arraycopy(days, 0, rt.days, 0, days.length);
1084 rt.startHour = startHour;
1085 rt.startMinute = startMinute;
1086 rt.endHour = endHour;
1087 rt.endMinute = endMinute;
1088 rt.exitAtAlarm = exitAtAlarm;
1089 rt.nextAlarm = nextAlarm;
1094 public String toString() {
1095 return "ScheduleInfo{" +
1096 "days=" + Arrays.toString(days) +
1097 ", startHour=" + startHour +
1098 ", startMinute=" + startMinute +
1099 ", endHour=" + endHour +
1100 ", endMinute=" + endMinute +
1101 ", exitAtAlarm=" + exitAtAlarm +
1102 ", nextAlarm=" + ts(nextAlarm) +
1106 protected static String ts(long time) {
1107 return new Date(time) + " (" + time + ")";
1111 // ==== Built-in system condition: event ====
1113 public static final String EVENT_PATH = "event";
1115 public static Uri toEventConditionId(EventInfo event) {
1116 return new Uri.Builder().scheme(Condition.SCHEME)
1117 .authority(SYSTEM_AUTHORITY)
1118 .appendPath(EVENT_PATH)
1119 .appendQueryParameter("userId", Long.toString(event.userId))
1120 .appendQueryParameter("calendar", event.calendar != null ? event.calendar : "")
1121 .appendQueryParameter("reply", Integer.toString(event.reply))
1125 public static boolean isValidEventConditionId(Uri conditionId) {
1126 return tryParseEventConditionId(conditionId) != null;
1129 public static EventInfo tryParseEventConditionId(Uri conditionId) {
1130 final boolean isEvent = conditionId != null
1131 && conditionId.getScheme().equals(Condition.SCHEME)
1132 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
1133 && conditionId.getPathSegments().size() == 1
1134 && conditionId.getPathSegments().get(0).equals(EVENT_PATH);
1135 if (!isEvent) return null;
1136 final EventInfo rt = new EventInfo();
1137 rt.userId = tryParseInt(conditionId.getQueryParameter("userId"), UserHandle.USER_NULL);
1138 rt.calendar = conditionId.getQueryParameter("calendar");
1139 if (TextUtils.isEmpty(rt.calendar) || tryParseLong(rt.calendar, -1L) != -1L) {
1142 rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0);
1146 public static ComponentName getEventConditionProvider() {
1147 return new ComponentName(SYSTEM_AUTHORITY, "EventConditionProvider");
1150 public static class EventInfo {
1151 public static final int REPLY_ANY_EXCEPT_NO = 0;
1152 public static final int REPLY_YES_OR_MAYBE = 1;
1153 public static final int REPLY_YES = 2;
1155 public int userId = UserHandle.USER_NULL; // USER_NULL = unspecified - use current user
1156 public String calendar; // CalendarContract.Calendars.OWNER_ACCOUNT, or null for any
1160 public int hashCode() {
1165 public boolean equals(Object o) {
1166 if (!(o instanceof EventInfo)) return false;
1167 final EventInfo other = (EventInfo) o;
1168 return userId == other.userId
1169 && Objects.equals(calendar, other.calendar)
1170 && reply == other.reply;
1173 public EventInfo copy() {
1174 final EventInfo rt = new EventInfo();
1176 rt.calendar = calendar;
1181 public static int resolveUserId(int userId) {
1182 return userId == UserHandle.USER_NULL ? ActivityManager.getCurrentUser() : userId;
1186 // ==== End built-in system conditions ====
1188 private static int[] tryParseHourAndMinute(String value) {
1189 if (TextUtils.isEmpty(value)) return null;
1190 final int i = value.indexOf('.');
1191 if (i < 1 || i >= value.length() - 1) return null;
1192 final int hour = tryParseInt(value.substring(0, i), -1);
1193 final int minute = tryParseInt(value.substring(i + 1), -1);
1194 return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
1197 private static int tryParseZenMode(String value, int defValue) {
1198 final int rt = tryParseInt(value, defValue);
1199 return Global.isValidZenMode(rt) ? rt : defValue;
1202 public static String newRuleId() {
1203 return UUID.randomUUID().toString().replace("-", "");
1207 * Gets the name of the app associated with owner
1209 public static String getOwnerCaption(Context context, String owner) {
1210 final PackageManager pm = context.getPackageManager();
1212 final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
1214 final CharSequence seq = info.loadLabel(pm);
1216 final String str = seq.toString().trim();
1217 if (str.length() > 0) {
1222 } catch (Throwable e) {
1223 Slog.w(TAG, "Error loading owner caption", e);
1228 public static String getConditionSummary(Context context, ZenModeConfig config,
1229 int userHandle, boolean shortVersion) {
1230 return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion);
1233 private static String getConditionLine(Context context, ZenModeConfig config,
1234 int userHandle, boolean useLine1, boolean shortVersion) {
1235 if (config == null) return "";
1236 String summary = "";
1237 if (config.manualRule != null) {
1238 final Uri id = config.manualRule.conditionId;
1239 if (config.manualRule.enabler != null) {
1240 summary = getOwnerCaption(context, config.manualRule.enabler);
1243 summary = context.getString(com.android.internal.R.string.zen_mode_forever);
1245 final long time = tryParseCountdownConditionId(id);
1246 Condition c = config.manualRule.condition;
1248 final long now = System.currentTimeMillis();
1249 final long span = time - now;
1250 c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS),
1251 userHandle, shortVersion);
1253 final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
1254 summary = TextUtils.isEmpty(rt) ? "" : rt;
1258 for (ZenRule automaticRule : config.automaticRules.values()) {
1259 if (automaticRule.isAutomaticActive()) {
1260 if (summary.isEmpty()) {
1261 summary = automaticRule.name;
1263 summary = context.getResources()
1264 .getString(R.string.zen_mode_rule_name_combination, summary,
1265 automaticRule.name);
1273 public static class ZenRule implements Parcelable {
1274 public boolean enabled;
1275 public boolean snoozing; // user manually disabled this instance
1276 public String name; // required for automatic
1278 public Uri conditionId; // required for automatic
1279 public Condition condition; // optional
1280 public ComponentName component; // optional
1281 public String id; // required for automatic (unique)
1282 public long creationTime; // required for automatic
1283 public String enabler; // package name, only used for manual rules.
1285 public ZenRule() { }
1287 public ZenRule(Parcel source) {
1288 enabled = source.readInt() == 1;
1289 snoozing = source.readInt() == 1;
1290 if (source.readInt() == 1) {
1291 name = source.readString();
1293 zenMode = source.readInt();
1294 conditionId = source.readParcelable(null);
1295 condition = source.readParcelable(null);
1296 component = source.readParcelable(null);
1297 if (source.readInt() == 1) {
1298 id = source.readString();
1300 creationTime = source.readLong();
1301 if (source.readInt() == 1) {
1302 enabler = source.readString();
1307 public int describeContents() {
1312 public void writeToParcel(Parcel dest, int flags) {
1313 dest.writeInt(enabled ? 1 : 0);
1314 dest.writeInt(snoozing ? 1 : 0);
1317 dest.writeString(name);
1321 dest.writeInt(zenMode);
1322 dest.writeParcelable(conditionId, 0);
1323 dest.writeParcelable(condition, 0);
1324 dest.writeParcelable(component, 0);
1327 dest.writeString(id);
1331 dest.writeLong(creationTime);
1332 if (enabler != null) {
1334 dest.writeString(enabler);
1341 public String toString() {
1342 return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
1343 .append("enabled=").append(enabled)
1344 .append(",snoozing=").append(snoozing)
1345 .append(",name=").append(name)
1346 .append(",zenMode=").append(Global.zenModeToString(zenMode))
1347 .append(",conditionId=").append(conditionId)
1348 .append(",condition=").append(condition)
1349 .append(",component=").append(component)
1350 .append(",id=").append(id)
1351 .append(",creationTime=").append(creationTime)
1352 .append(",enabler=").append(enabler)
1353 .append(']').toString();
1357 public void writeToProto(ProtoOutputStream proto, long fieldId) {
1358 final long token = proto.start(fieldId);
1360 proto.write(ZenRuleProto.ID, id);
1361 proto.write(ZenRuleProto.NAME, name);
1362 proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime);
1363 proto.write(ZenRuleProto.ENABLED, enabled);
1364 proto.write(ZenRuleProto.ENABLER, enabler);
1365 proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
1366 proto.write(ZenRuleProto.ZEN_MODE, zenMode);
1367 if (conditionId != null) {
1368 proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString());
1370 if (condition != null) {
1371 condition.writeToProto(proto, ZenRuleProto.CONDITION);
1373 if (component != null) {
1374 component.writeToProto(proto, ZenRuleProto.COMPONENT);
1380 private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
1381 if (d == null) return;
1384 d.addLine(item, "insert");
1388 from.appendDiff(d, item, to);
1391 private void appendDiff(Diff d, String item, ZenRule to) {
1393 d.addLine(item, "delete");
1396 if (enabled != to.enabled) {
1397 d.addLine(item, "enabled", enabled, to.enabled);
1399 if (snoozing != to.snoozing) {
1400 d.addLine(item, "snoozing", snoozing, to.snoozing);
1402 if (!Objects.equals(name, to.name)) {
1403 d.addLine(item, "name", name, to.name);
1405 if (zenMode != to.zenMode) {
1406 d.addLine(item, "zenMode", zenMode, to.zenMode);
1408 if (!Objects.equals(conditionId, to.conditionId)) {
1409 d.addLine(item, "conditionId", conditionId, to.conditionId);
1411 if (!Objects.equals(condition, to.condition)) {
1412 d.addLine(item, "condition", condition, to.condition);
1414 if (!Objects.equals(component, to.component)) {
1415 d.addLine(item, "component", component, to.component);
1417 if (!Objects.equals(id, to.id)) {
1418 d.addLine(item, "id", id, to.id);
1420 if (creationTime != to.creationTime) {
1421 d.addLine(item, "creationTime", creationTime, to.creationTime);
1423 if (enabler != to.enabler) {
1424 d.addLine(item, "enabler", enabler, to.enabler);
1429 public boolean equals(Object o) {
1430 if (!(o instanceof ZenRule)) return false;
1431 if (o == this) return true;
1432 final ZenRule other = (ZenRule) o;
1433 return other.enabled == enabled
1434 && other.snoozing == snoozing
1435 && Objects.equals(other.name, name)
1436 && other.zenMode == zenMode
1437 && Objects.equals(other.conditionId, conditionId)
1438 && Objects.equals(other.condition, condition)
1439 && Objects.equals(other.component, component)
1440 && Objects.equals(other.id, id)
1441 && other.creationTime == creationTime
1442 && Objects.equals(other.enabler, enabler);
1446 public int hashCode() {
1447 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
1448 component, id, creationTime, enabler);
1451 public boolean isAutomaticActive() {
1452 return enabled && !snoozing && component != null && isTrueOrUnknown();
1455 public boolean isTrueOrUnknown() {
1456 return condition != null && (condition.state == Condition.STATE_TRUE
1457 || condition.state == Condition.STATE_UNKNOWN);
1460 public static final Parcelable.Creator<ZenRule> CREATOR
1461 = new Parcelable.Creator<ZenRule>() {
1463 public ZenRule createFromParcel(Parcel source) {
1464 return new ZenRule(source);
1467 public ZenRule[] newArray(int size) {
1468 return new ZenRule[size];
1473 public static class Diff {
1474 private final ArrayList<String> lines = new ArrayList<>();
1477 public String toString() {
1478 final StringBuilder sb = new StringBuilder("Diff[");
1479 final int N = lines.size();
1480 for (int i = 0; i < N; i++) {
1484 sb.append(lines.get(i));
1486 return sb.append(']').toString();
1489 private Diff addLine(String item, String action) {
1490 lines.add(item + ":" + action);
1494 public Diff addLine(String item, String subitem, Object from, Object to) {
1495 return addLine(item + "." + subitem, from, to);
1498 public Diff addLine(String item, Object from, Object to) {
1499 return addLine(item, from + "->" + to);
1504 * Determines whether dnd behavior should mute all notification/ringer sounds
1505 * (sounds associated with ringer volume discluding system)
1507 public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(NotificationManager.Policy
1509 boolean allowReminders = (policy.priorityCategories
1510 & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
1511 boolean allowCalls = (policy.priorityCategories
1512 & NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) != 0;
1513 boolean allowMessages = (policy.priorityCategories
1514 & NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
1515 boolean allowEvents = (policy.priorityCategories
1516 & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0;
1517 boolean allowRepeatCallers = (policy.priorityCategories
1518 & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
1519 boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
1520 return !allowReminders && !allowCalls && !allowMessages && !allowEvents
1521 && !allowRepeatCallers && !areChannelsBypassingDnd;
1525 * Determines if DND is currently overriding the ringer
1527 public static boolean isZenOverridingRinger(int zen, ZenModeConfig zenConfig) {
1528 return zen == Global.ZEN_MODE_NO_INTERRUPTIONS
1529 || zen == Global.ZEN_MODE_ALARMS
1530 || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
1531 && ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(zenConfig));
1535 * Determines whether dnd behavior should mute all sounds controlled by ringer
1537 public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) {
1538 return !config.allowReminders && !config.allowCalls && !config.allowMessages
1539 && !config.allowEvents && !config.allowRepeatCallers
1540 && !config.areChannelsBypassingDnd;
1544 * Determines whether all dnd mutes all sounds
1546 public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) {
1547 return !config.allowAlarms && !config.allowMedia && !config.allowSystem
1548 && areAllPriorityOnlyNotificationZenSoundsMuted(config);
1552 * Returns a description of the current do not disturb settings from config.
1553 * - If turned on manually and end time is known, returns end time.
1554 * - If turned on manually and end time is on forever until turned off, return null if
1555 * describeForeverCondition is false, else return String describing indefinite behavior
1556 * - If turned on by an automatic rule, returns the automatic rule name.
1557 * - If on due to an app, returns the app name.
1558 * - If there's a combination of rules/apps that trigger, then shows the one that will
1559 * last the longest if applicable.
1560 * @return null if DND is off or describeForeverCondition is false and
1561 * DND is on forever (until turned off)
1563 public static String getDescription(Context context, boolean zenOn, ZenModeConfig config,
1564 boolean describeForeverCondition) {
1565 if (!zenOn || config == null) {
1569 String secondaryText = "";
1570 long latestEndTime = -1;
1572 // DND turned on by manual rule
1573 if (config.manualRule != null) {
1574 final Uri id = config.manualRule.conditionId;
1575 if (config.manualRule.enabler != null) {
1576 // app triggered manual rule
1577 String appName = getOwnerCaption(context, config.manualRule.enabler);
1578 if (!appName.isEmpty()) {
1579 secondaryText = appName;
1583 // Do not disturb manually triggered to remain on forever until turned off
1584 if (describeForeverCondition) {
1585 return context.getString(R.string.zen_mode_forever);
1590 latestEndTime = tryParseCountdownConditionId(id);
1591 if (latestEndTime > 0) {
1592 final CharSequence formattedTime = getFormattedTime(context,
1593 latestEndTime, isToday(latestEndTime),
1594 context.getUserId());
1595 secondaryText = context.getString(R.string.zen_mode_until, formattedTime);
1601 // DND turned on by an automatic rule
1602 for (ZenRule automaticRule : config.automaticRules.values()) {
1603 if (automaticRule.isAutomaticActive()) {
1604 if (isValidEventConditionId(automaticRule.conditionId)
1605 || isValidScheduleConditionId(automaticRule.conditionId)) {
1606 // set text if automatic rule end time is the latest active rule end time
1607 long endTime = parseAutomaticRuleEndTime(context, automaticRule.conditionId);
1608 if (endTime > latestEndTime) {
1609 latestEndTime = endTime;
1610 secondaryText = automaticRule.name;
1613 // set text if 3rd party rule
1614 return automaticRule.name;
1619 return !secondaryText.equals("") ? secondaryText : null;
1622 private static long parseAutomaticRuleEndTime(Context context, Uri id) {
1623 if (isValidEventConditionId(id)) {
1624 // cannot look up end times for events
1625 return Long.MAX_VALUE;
1628 if (isValidScheduleConditionId(id)) {
1629 ScheduleCalendar schedule = toScheduleCalendar(id);
1630 long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis());
1632 // check if automatic rule will end on next alarm
1633 if (schedule.exitAtAlarm()) {
1634 long nextAlarm = getNextAlarm(context);
1635 schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm);
1636 if (schedule.shouldExitForAlarm(endTimeMs)) {
1647 private static long getNextAlarm(Context context) {
1648 final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
1649 final AlarmManager.AlarmClockInfo info = alarms.getNextAlarmClock(context.getUserId());
1650 return info != null ? info.getTriggerTime() : 0;