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