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