Home | History | Annotate | Download | only in notification
      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 
    126     public ZenModeConfig() { }
    127 
    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
    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
    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 
    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 
    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 
    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 
    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 
    264     private static boolean isValidManualRule(ZenRule rule) {
    265         return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
    266     }
    267 
    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 
    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 
    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 
    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
    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
    325     public int hashCode() {
    326         return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
    327                 allowMessagesFrom, allowReminders, allowEvents, user, automaticRules, manualRule);
    328     }
    329 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    520     public static boolean isValidHour(int val) {
    521         return val >= 0 && val < 24;
    522     }
    523 
    524     public static boolean isValidMinute(int val) {
    525         return val >= 0 && val < 60;
    526     }
    527 
    528     private static boolean isValidSource(int source) {
    529         return source >= SOURCE_ANYONE && source <= MAX_SOURCE;
    530     }
    531 
    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 
    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 
    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 
    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 
    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
    564     public int describeContents() {
    565         return 0;
    566     }
    567 
    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 
    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 
    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 
    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 
    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 
    647     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
    648         return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/);
    649     }
    650 
    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 
    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 
    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 
    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 
    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 
    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 
    735     public static boolean isValidScheduleConditionId(Uri conditionId) {
    736         return tryParseScheduleConditionId(conditionId) != null;
    737     }
    738 
    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
    766         public int hashCode() {
    767             return 0;
    768         }
    769 
    770         @Override
    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 
    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 
    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 
    809     public static boolean isValidEventConditionId(Uri conditionId) {
    810         return tryParseEventConditionId(conditionId) != null;
    811     }
    812 
    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
    840         public int hashCode() {
    841             return 0;
    842         }
    843 
    844         @Override
    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 
    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 
    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 
    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 
    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 
    882     public String newRuleId() {
    883         return UUID.randomUUID().toString().replace("-", "");
    884     }
    885 
    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 
    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 
    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 
    939         public ZenRule() { }
    940 
    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
    954         public int describeContents() {
    955             return 0;
    956         }
    957 
    958         @Override
    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
    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 
    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 
    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
   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
   1041         public int hashCode() {
   1042             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
   1043                     component);
   1044         }
   1045 
   1046         public boolean isAutomaticActive() {
   1047             return enabled && !snoozing && component != null && isTrueOrUnknown();
   1048         }
   1049 
   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 
   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 
   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 
   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 {
   1181         ZenModeConfig migrate(XmlV1 v1);
   1182     }
   1183 
   1184     public static class Diff {
   1185         private final ArrayList<String> lines = new ArrayList<>();
   1186 
   1187         @Override
   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 
   1200         private Diff addLine(String item, String action) {
   1201             lines.add(item + ":" + action);
   1202             return this;
   1203         }
   1204 
   1205         public Diff addLine(String item, String subitem, Object from, Object to) {
   1206             return addLine(item + "." + subitem, from, to);
   1207         }
   1208 
   1209         public Diff addLine(String item, Object from, Object to) {
   1210             return addLine(item, from + "->" + to);
   1211         }
   1212     }
   1213 
   1214 }
   1215