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 
17 package android.service.notification;
18 
19 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
20 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
21 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE;
22 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
23 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
24 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
25 
26 import android.app.ActivityManager;
27 import android.app.AlarmManager;
28 import android.app.NotificationManager;
29 import android.app.NotificationManager.Policy;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.res.Resources;
36 import android.net.Uri;
37 import android.os.Parcel;
38 import android.os.Parcelable;
39 import android.os.UserHandle;
40 import android.provider.Settings.Global;
41 import android.text.TextUtils;
42 import android.text.format.DateFormat;
43 import android.util.ArrayMap;
44 import android.util.ArraySet;
45 import android.util.Slog;
46 import android.util.proto.ProtoOutputStream;
47 
48 import com.android.internal.R;
49 
50 import org.xmlpull.v1.XmlPullParser;
51 import org.xmlpull.v1.XmlPullParserException;
52 import org.xmlpull.v1.XmlSerializer;
53 
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Calendar;
58 import java.util.Date;
59 import java.util.GregorianCalendar;
60 import java.util.List;
61 import java.util.Locale;
62 import java.util.Objects;
63 import java.util.TimeZone;
64 import java.util.UUID;
65 
66 /**
67  * Persisted configuration for zen mode.
68  *
69  * @hide
70  */
71 public class ZenModeConfig implements Parcelable {
72     private static String TAG = "ZenModeConfig";
73 
74     public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
75     public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
76     public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
77     public static final int MAX_SOURCE = SOURCE_STAR;
78     private static final int DEFAULT_SOURCE = SOURCE_CONTACT;
79     private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
80 
81     public static final String MANUAL_RULE_ID = "MANUAL_RULE";
82     public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
83     public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
84     public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
85             EVENTS_DEFAULT_RULE_ID);
86 
87     public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
88             Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
89 
90     public static final int[] MINUTE_BUCKETS = generateMinuteBuckets();
91     private static final int SECONDS_MS = 1000;
92     private static final int MINUTES_MS = 60 * SECONDS_MS;
93     private static final int DAY_MINUTES = 24 * 60;
94     private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
95 
96     // Default allow categories set in readXml() from default_zen_mode_config.xml,
97     // fallback/upgrade values:
98     private static final boolean DEFAULT_ALLOW_ALARMS = true;
99     private static final boolean DEFAULT_ALLOW_MEDIA = true;
100     private static final boolean DEFAULT_ALLOW_SYSTEM = false;
101     private static final boolean DEFAULT_ALLOW_CALLS = true;
102     private static final boolean DEFAULT_ALLOW_MESSAGES = false;
103     private static final boolean DEFAULT_ALLOW_REMINDERS = false;
104     private static final boolean DEFAULT_ALLOW_EVENTS = false;
105     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true;
106     private static final boolean DEFAULT_ALLOW_CONV = false;
107     private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_NONE;
108     private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
109     private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 0;
110 
111     public static final int XML_VERSION = 8;
112     public static final String ZEN_TAG = "zen";
113     private static final String ZEN_ATT_VERSION = "version";
114     private static final String ZEN_ATT_USER = "user";
115     private static final String ALLOW_TAG = "allow";
116     private static final String ALLOW_ATT_ALARMS = "alarms";
117     private static final String ALLOW_ATT_MEDIA = "media";
118     private static final String ALLOW_ATT_SYSTEM = "system";
119     private static final String ALLOW_ATT_CALLS = "calls";
120     private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
121     private static final String ALLOW_ATT_MESSAGES = "messages";
122     private static final String ALLOW_ATT_FROM = "from";
123     private static final String ALLOW_ATT_CALLS_FROM = "callsFrom";
124     private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom";
125     private static final String ALLOW_ATT_REMINDERS = "reminders";
126     private static final String ALLOW_ATT_EVENTS = "events";
127     private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
128     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
129     private static final String ALLOW_ATT_CONV = "convos";
130     private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
131     private static final String DISALLOW_TAG = "disallow";
132     private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
133     private static final String STATE_TAG = "state";
134     private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
135 
136     // zen policy visual effects attributes
137     private static final String SHOW_ATT_FULL_SCREEN_INTENT = "showFullScreenIntent";
138     private static final String SHOW_ATT_LIGHTS = "showLights";
139     private static final String SHOW_ATT_PEEK = "shoePeek";
140     private static final String SHOW_ATT_STATUS_BAR_ICONS = "showStatusBarIcons";
141     private static final String SHOW_ATT_BADGES = "showBadges";
142     private static final String SHOW_ATT_AMBIENT = "showAmbient";
143     private static final String SHOW_ATT_NOTIFICATION_LIST = "showNotificationList";
144 
145     private static final String CONDITION_ATT_ID = "id";
146     private static final String CONDITION_ATT_SUMMARY = "summary";
147     private static final String CONDITION_ATT_LINE1 = "line1";
148     private static final String CONDITION_ATT_LINE2 = "line2";
149     private static final String CONDITION_ATT_ICON = "icon";
150     private static final String CONDITION_ATT_STATE = "state";
151     private static final String CONDITION_ATT_FLAGS = "flags";
152 
153     private static final String ZEN_POLICY_TAG = "zen_policy";
154 
155     private static final String MANUAL_TAG = "manual";
156     private static final String AUTOMATIC_TAG = "automatic";
157 
158     private static final String RULE_ATT_ID = "ruleId";
159     private static final String RULE_ATT_ENABLED = "enabled";
160     private static final String RULE_ATT_SNOOZING = "snoozing";
161     private static final String RULE_ATT_NAME = "name";
162     private static final String RULE_ATT_COMPONENT = "component";
163     private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity";
164     private static final String RULE_ATT_ZEN = "zen";
165     private static final String RULE_ATT_CONDITION_ID = "conditionId";
166     private static final String RULE_ATT_CREATION_TIME = "creationTime";
167     private static final String RULE_ATT_ENABLER = "enabler";
168     private static final String RULE_ATT_MODIFIED = "modified";
169 
170     @UnsupportedAppUsage
171     public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
172     public boolean allowMedia = DEFAULT_ALLOW_MEDIA;
173     public boolean allowSystem = DEFAULT_ALLOW_SYSTEM;
174     public boolean allowCalls = DEFAULT_ALLOW_CALLS;
175     public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
176     public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
177     public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
178     public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
179     public int allowCallsFrom = DEFAULT_CALLS_SOURCE;
180     public int allowMessagesFrom = DEFAULT_SOURCE;
181     public boolean allowConversations = DEFAULT_ALLOW_CONV;
182     public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM;
183     public int user = UserHandle.USER_SYSTEM;
184     public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
185     public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
186     public int version;
187 
188     public ZenRule manualRule;
189     @UnsupportedAppUsage
190     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
191 
192     @UnsupportedAppUsage
ZenModeConfig()193     public ZenModeConfig() { }
194 
ZenModeConfig(Parcel source)195     public ZenModeConfig(Parcel source) {
196         allowCalls = source.readInt() == 1;
197         allowRepeatCallers = source.readInt() == 1;
198         allowMessages = source.readInt() == 1;
199         allowReminders = source.readInt() == 1;
200         allowEvents = source.readInt() == 1;
201         allowCallsFrom = source.readInt();
202         allowMessagesFrom = source.readInt();
203         user = source.readInt();
204         manualRule = source.readParcelable(null);
205         final int len = source.readInt();
206         if (len > 0) {
207             final String[] ids = new String[len];
208             final ZenRule[] rules = new ZenRule[len];
209             source.readStringArray(ids);
210             source.readTypedArray(rules, ZenRule.CREATOR);
211             for (int i = 0; i < len; i++) {
212                 automaticRules.put(ids[i], rules[i]);
213             }
214         }
215         allowAlarms = source.readInt() == 1;
216         allowMedia = source.readInt() == 1;
217         allowSystem = source.readInt() == 1;
218         suppressedVisualEffects = source.readInt();
219         areChannelsBypassingDnd = source.readInt() == 1;
220         allowConversations = source.readBoolean();
221         allowConversationsFrom = source.readInt();
222     }
223 
224     @Override
writeToParcel(Parcel dest, int flags)225     public void writeToParcel(Parcel dest, int flags) {
226         dest.writeInt(allowCalls ? 1 : 0);
227         dest.writeInt(allowRepeatCallers ? 1 : 0);
228         dest.writeInt(allowMessages ? 1 : 0);
229         dest.writeInt(allowReminders ? 1 : 0);
230         dest.writeInt(allowEvents ? 1 : 0);
231         dest.writeInt(allowCallsFrom);
232         dest.writeInt(allowMessagesFrom);
233         dest.writeInt(user);
234         dest.writeParcelable(manualRule, 0);
235         if (!automaticRules.isEmpty()) {
236             final int len = automaticRules.size();
237             final String[] ids = new String[len];
238             final ZenRule[] rules = new ZenRule[len];
239             for (int i = 0; i < len; i++) {
240                 ids[i] = automaticRules.keyAt(i);
241                 rules[i] = automaticRules.valueAt(i);
242             }
243             dest.writeInt(len);
244             dest.writeStringArray(ids);
245             dest.writeTypedArray(rules, 0);
246         } else {
247             dest.writeInt(0);
248         }
249         dest.writeInt(allowAlarms ? 1 : 0);
250         dest.writeInt(allowMedia ? 1 : 0);
251         dest.writeInt(allowSystem ? 1 : 0);
252         dest.writeInt(suppressedVisualEffects);
253         dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
254         dest.writeBoolean(allowConversations);
255         dest.writeInt(allowConversationsFrom);
256     }
257 
258     @Override
toString()259     public String toString() {
260         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
261                 .append("user=").append(user)
262                 .append(",allowAlarms=").append(allowAlarms)
263                 .append(",allowMedia=").append(allowMedia)
264                 .append(",allowSystem=").append(allowSystem)
265                 .append(",allowReminders=").append(allowReminders)
266                 .append(",allowEvents=").append(allowEvents)
267                 .append(",allowCalls=").append(allowCalls)
268                 .append(",allowRepeatCallers=").append(allowRepeatCallers)
269                 .append(",allowMessages=").append(allowMessages)
270                 .append(",allowConversations=").append(allowConversations)
271                 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
272                 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
273                 .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString
274                         (allowConversationsFrom))
275                 .append(",suppressedVisualEffects=").append(suppressedVisualEffects)
276                 .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd)
277                 .append(",\nautomaticRules=").append(rulesToString())
278                 .append(",\nmanualRule=").append(manualRule)
279                 .append(']').toString();
280     }
281 
rulesToString()282     private String rulesToString() {
283         if (automaticRules.isEmpty()) {
284             return "{}";
285         }
286 
287         StringBuilder buffer = new StringBuilder(automaticRules.size() * 28);
288         buffer.append('{');
289         for (int i = 0; i < automaticRules.size(); i++) {
290             if (i > 0) {
291                 buffer.append(",\n");
292             }
293             Object value = automaticRules.valueAt(i);
294             buffer.append(value);
295         }
296         buffer.append('}');
297         return buffer.toString();
298     }
299 
diff(ZenModeConfig to)300     public Diff diff(ZenModeConfig to) {
301         final Diff d = new Diff();
302         if (to == null) {
303             return d.addLine("config", "delete");
304         }
305         if (user != to.user) {
306             d.addLine("user", user, to.user);
307         }
308         if (allowAlarms != to.allowAlarms) {
309             d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
310         }
311         if (allowMedia != to.allowMedia) {
312             d.addLine("allowMedia", allowMedia, to.allowMedia);
313         }
314         if (allowSystem != to.allowSystem) {
315             d.addLine("allowSystem", allowSystem, to.allowSystem);
316         }
317         if (allowCalls != to.allowCalls) {
318             d.addLine("allowCalls", allowCalls, to.allowCalls);
319         }
320         if (allowReminders != to.allowReminders) {
321             d.addLine("allowReminders", allowReminders, to.allowReminders);
322         }
323         if (allowEvents != to.allowEvents) {
324             d.addLine("allowEvents", allowEvents, to.allowEvents);
325         }
326         if (allowRepeatCallers != to.allowRepeatCallers) {
327             d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
328         }
329         if (allowMessages != to.allowMessages) {
330             d.addLine("allowMessages", allowMessages, to.allowMessages);
331         }
332         if (allowCallsFrom != to.allowCallsFrom) {
333             d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
334         }
335         if (allowMessagesFrom != to.allowMessagesFrom) {
336             d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
337         }
338         if (suppressedVisualEffects != to.suppressedVisualEffects) {
339             d.addLine("suppressedVisualEffects", suppressedVisualEffects,
340                     to.suppressedVisualEffects);
341         }
342         final ArraySet<String> allRules = new ArraySet<>();
343         addKeys(allRules, automaticRules);
344         addKeys(allRules, to.automaticRules);
345         final int N = allRules.size();
346         for (int i = 0; i < N; i++) {
347             final String rule = allRules.valueAt(i);
348             final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
349             final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
350             ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
351         }
352         ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
353 
354         if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
355             d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
356                     to.areChannelsBypassingDnd);
357         }
358         return d;
359     }
360 
diff(ZenModeConfig from, ZenModeConfig to)361     public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
362         if (from == null) {
363             final Diff d = new Diff();
364             if (to != null) {
365                 d.addLine("config", "insert");
366             }
367             return d;
368         }
369         return from.diff(to);
370     }
371 
addKeys(ArraySet<T> set, ArrayMap<T, ?> map)372     private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
373         if (map != null) {
374             for (int i = 0; i < map.size(); i++) {
375                 set.add(map.keyAt(i));
376             }
377         }
378     }
379 
isValid()380     public boolean isValid() {
381         if (!isValidManualRule(manualRule)) return false;
382         final int N = automaticRules.size();
383         for (int i = 0; i < N; i++) {
384             if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
385         }
386         return true;
387     }
388 
isValidManualRule(ZenRule rule)389     private static boolean isValidManualRule(ZenRule rule) {
390         return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
391     }
392 
isValidAutomaticRule(ZenRule rule)393     private static boolean isValidAutomaticRule(ZenRule rule) {
394         return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
395                 && rule.conditionId != null && sameCondition(rule);
396     }
397 
sameCondition(ZenRule rule)398     private static boolean sameCondition(ZenRule rule) {
399         if (rule == null) return false;
400         if (rule.conditionId == null) {
401             return rule.condition == null;
402         } else {
403             return rule.condition == null || rule.conditionId.equals(rule.condition.id);
404         }
405     }
406 
generateMinuteBuckets()407     private static int[] generateMinuteBuckets() {
408         final int maxHrs = 12;
409         final int[] buckets = new int[maxHrs + 3];
410         buckets[0] = 15;
411         buckets[1] = 30;
412         buckets[2] = 45;
413         for (int i = 1; i <= maxHrs; i++) {
414             buckets[2 + i] = 60 * i;
415         }
416         return buckets;
417     }
418 
sourceToString(int source)419     public static String sourceToString(int source) {
420         switch (source) {
421             case SOURCE_ANYONE:
422                 return "anyone";
423             case SOURCE_CONTACT:
424                 return "contacts";
425             case SOURCE_STAR:
426                 return "stars";
427             default:
428                 return "UNKNOWN";
429         }
430     }
431 
432     @Override
equals(Object o)433     public boolean equals(Object o) {
434         if (!(o instanceof ZenModeConfig)) return false;
435         if (o == this) return true;
436         final ZenModeConfig other = (ZenModeConfig) o;
437         return other.allowAlarms == allowAlarms
438                 && other.allowMedia == allowMedia
439                 && other.allowSystem == allowSystem
440                 && other.allowCalls == allowCalls
441                 && other.allowRepeatCallers == allowRepeatCallers
442                 && other.allowMessages == allowMessages
443                 && other.allowCallsFrom == allowCallsFrom
444                 && other.allowMessagesFrom == allowMessagesFrom
445                 && other.allowReminders == allowReminders
446                 && other.allowEvents == allowEvents
447                 && other.user == user
448                 && Objects.equals(other.automaticRules, automaticRules)
449                 && Objects.equals(other.manualRule, manualRule)
450                 && other.suppressedVisualEffects == suppressedVisualEffects
451                 && other.areChannelsBypassingDnd == areChannelsBypassingDnd
452                 && other.allowConversations == allowConversations
453                 && other.allowConversationsFrom == allowConversationsFrom;
454     }
455 
456     @Override
hashCode()457     public int hashCode() {
458         return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls,
459                 allowRepeatCallers, allowMessages,
460                 allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
461                 user, automaticRules, manualRule,
462                 suppressedVisualEffects, areChannelsBypassingDnd, allowConversations,
463                 allowConversationsFrom);
464     }
465 
toDayList(int[] days)466     private static String toDayList(int[] days) {
467         if (days == null || days.length == 0) return "";
468         final StringBuilder sb = new StringBuilder();
469         for (int i = 0; i < days.length; i++) {
470             if (i > 0) sb.append('.');
471             sb.append(days[i]);
472         }
473         return sb.toString();
474     }
475 
tryParseDayList(String dayList, String sep)476     private static int[] tryParseDayList(String dayList, String sep) {
477         if (dayList == null) return null;
478         final String[] tokens = dayList.split(sep);
479         if (tokens.length == 0) return null;
480         final int[] rt = new int[tokens.length];
481         for (int i = 0; i < tokens.length; i++) {
482             final int day = tryParseInt(tokens[i], -1);
483             if (day == -1) return null;
484             rt[i] = day;
485         }
486         return rt;
487     }
488 
tryParseInt(String value, int defValue)489     private static int tryParseInt(String value, int defValue) {
490         if (TextUtils.isEmpty(value)) return defValue;
491         try {
492             return Integer.parseInt(value);
493         } catch (NumberFormatException e) {
494             return defValue;
495         }
496     }
497 
tryParseLong(String value, long defValue)498     private static long tryParseLong(String value, long defValue) {
499         if (TextUtils.isEmpty(value)) return defValue;
500         try {
501             return Long.parseLong(value);
502         } catch (NumberFormatException e) {
503             return defValue;
504         }
505     }
506 
tryParseLong(String value, Long defValue)507     private static Long tryParseLong(String value, Long defValue) {
508         if (TextUtils.isEmpty(value)) return defValue;
509         try {
510             return Long.parseLong(value);
511         } catch (NumberFormatException e) {
512             return defValue;
513         }
514     }
515 
readXml(XmlPullParser parser)516     public static ZenModeConfig readXml(XmlPullParser parser)
517             throws XmlPullParserException, IOException {
518         int type = parser.getEventType();
519         if (type != XmlPullParser.START_TAG) return null;
520         String tag = parser.getName();
521         if (!ZEN_TAG.equals(tag)) return null;
522         final ZenModeConfig rt = new ZenModeConfig();
523         rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
524         rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
525         boolean readSuppressedEffects = false;
526         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
527             tag = parser.getName();
528             if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
529                 return rt;
530             }
531             if (type == XmlPullParser.START_TAG) {
532                 if (ALLOW_TAG.equals(tag)) {
533                     rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
534                             DEFAULT_ALLOW_CALLS);
535                     rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
536                             DEFAULT_ALLOW_REPEAT_CALLERS);
537                     rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
538                             DEFAULT_ALLOW_MESSAGES);
539                     rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
540                             DEFAULT_ALLOW_REMINDERS);
541                     rt.allowConversations = safeBoolean(parser, ALLOW_ATT_CONV, DEFAULT_ALLOW_CONV);
542                     rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
543                     final int from = safeInt(parser, ALLOW_ATT_FROM, -1);
544                     final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1);
545                     final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1);
546                     rt.allowConversationsFrom = safeInt(parser, ALLOW_ATT_CONV_FROM,
547                             DEFAULT_ALLOW_CONV_FROM);
548                     if (isValidSource(callsFrom) && isValidSource(messagesFrom)) {
549                         rt.allowCallsFrom = callsFrom;
550                         rt.allowMessagesFrom = messagesFrom;
551                     } else if (isValidSource(from)) {
552                         Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from));
553                         rt.allowCallsFrom = from;
554                         rt.allowMessagesFrom = from;
555                     } else {
556                         rt.allowCallsFrom = DEFAULT_CALLS_SOURCE;
557                         rt.allowMessagesFrom = DEFAULT_SOURCE;
558                     }
559                     rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
560                     rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA,
561                             DEFAULT_ALLOW_MEDIA);
562                     rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM);
563 
564                     // migrate old suppressed visual effects fields, if they still exist in the xml
565                     Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF);
566                     if (allowWhenScreenOff != null) {
567                         readSuppressedEffects = true;
568                         if (!allowWhenScreenOff) {
569                             rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS
570                                     | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
571                         }
572                     }
573                     Boolean allowWhenScreenOn = unsafeBoolean(parser, ALLOW_ATT_SCREEN_ON);
574                     if (allowWhenScreenOn != null) {
575                         readSuppressedEffects = true;
576                         if (!allowWhenScreenOn) {
577                             rt.suppressedVisualEffects |= SUPPRESSED_EFFECT_PEEK;
578                         }
579                     }
580                     if (readSuppressedEffects) {
581                         Slog.d(TAG, "Migrated visual effects to " + rt.suppressedVisualEffects);
582                     }
583                 } else if (DISALLOW_TAG.equals(tag) && !readSuppressedEffects) {
584                     // only read from suppressed visual effects field if we haven't just migrated
585                     // the values from allowOn/allowOff, lest we wipe out those settings
586                     rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
587                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
588                 } else if (MANUAL_TAG.equals(tag)) {
589                     rt.manualRule = readRuleXml(parser);
590                 } else if (AUTOMATIC_TAG.equals(tag)) {
591                     final String id = parser.getAttributeValue(null, RULE_ATT_ID);
592                     final ZenRule automaticRule = readRuleXml(parser);
593                     if (id != null && automaticRule != null) {
594                         automaticRule.id = id;
595                         rt.automaticRules.put(id, automaticRule);
596                     }
597                 } else if (STATE_TAG.equals(tag)) {
598                     rt.areChannelsBypassingDnd = safeBoolean(parser,
599                             STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
600                 }
601             }
602         }
603         throw new IllegalStateException("Failed to reach END_DOCUMENT");
604     }
605 
606     /**
607      * Writes XML of current ZenModeConfig
608      * @param out serializer
609      * @param version uses XML_VERSION if version is null
610      * @throws IOException
611      */
writeXml(XmlSerializer out, Integer version)612     public void writeXml(XmlSerializer out, Integer version) throws IOException {
613         out.startTag(null, ZEN_TAG);
614         out.attribute(null, ZEN_ATT_VERSION, version == null
615                 ? Integer.toString(XML_VERSION) : Integer.toString(version));
616         out.attribute(null, ZEN_ATT_USER, Integer.toString(user));
617         out.startTag(null, ALLOW_TAG);
618         out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
619         out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers));
620         out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
621         out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
622         out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
623         out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom));
624         out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
625         out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
626         out.attribute(null, ALLOW_ATT_MEDIA, Boolean.toString(allowMedia));
627         out.attribute(null, ALLOW_ATT_SYSTEM, Boolean.toString(allowSystem));
628         out.attribute(null, ALLOW_ATT_CONV, Boolean.toString(allowConversations));
629         out.attribute(null, ALLOW_ATT_CONV_FROM, Integer.toString(allowConversationsFrom));
630         out.endTag(null, ALLOW_TAG);
631 
632         out.startTag(null, DISALLOW_TAG);
633         out.attribute(null, DISALLOW_ATT_VISUAL_EFFECTS, Integer.toString(suppressedVisualEffects));
634         out.endTag(null, DISALLOW_TAG);
635 
636         if (manualRule != null) {
637             out.startTag(null, MANUAL_TAG);
638             writeRuleXml(manualRule, out);
639             out.endTag(null, MANUAL_TAG);
640         }
641         final int N = automaticRules.size();
642         for (int i = 0; i < N; i++) {
643             final String id = automaticRules.keyAt(i);
644             final ZenRule automaticRule = automaticRules.valueAt(i);
645             out.startTag(null, AUTOMATIC_TAG);
646             out.attribute(null, RULE_ATT_ID, id);
647             writeRuleXml(automaticRule, out);
648             out.endTag(null, AUTOMATIC_TAG);
649         }
650 
651         out.startTag(null, STATE_TAG);
652         out.attribute(null, STATE_ATT_CHANNELS_BYPASSING_DND,
653                 Boolean.toString(areChannelsBypassingDnd));
654         out.endTag(null, STATE_TAG);
655 
656         out.endTag(null, ZEN_TAG);
657     }
658 
readRuleXml(XmlPullParser parser)659     public static ZenRule readRuleXml(XmlPullParser parser) {
660         final ZenRule rt = new ZenRule();
661         rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
662         rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
663         final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
664         rt.zenMode = tryParseZenMode(zen, -1);
665         if (rt.zenMode == -1) {
666             Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
667             return null;
668         }
669         rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
670         rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
671         rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY);
672         rt.pkg = (rt.component != null)
673                 ? rt.component.getPackageName()
674                 : (rt.configurationActivity != null)
675                         ? rt.configurationActivity.getPackageName()
676                         : null;
677         rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
678         rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
679         rt.condition = readConditionXml(parser);
680 
681         // all default rules and user created rules updated to zenMode important interruptions
682         if (rt.zenMode != Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
683                 && Condition.isValidId(rt.conditionId, SYSTEM_AUTHORITY)) {
684             Slog.i(TAG, "Updating zenMode of automatic rule " + rt.name);
685             rt.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
686         }
687         rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false);
688         rt.zenPolicy = readZenPolicyXml(parser);
689         return rt;
690     }
691 
writeRuleXml(ZenRule rule, XmlSerializer out)692     public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
693         out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
694         if (rule.name != null) {
695             out.attribute(null, RULE_ATT_NAME, rule.name);
696         }
697         out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
698         if (rule.component != null) {
699             out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
700         }
701         if (rule.configurationActivity != null) {
702             out.attribute(null, RULE_ATT_CONFIG_ACTIVITY,
703                     rule.configurationActivity.flattenToString());
704         }
705         if (rule.conditionId != null) {
706             out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
707         }
708         out.attribute(null, RULE_ATT_CREATION_TIME, Long.toString(rule.creationTime));
709         if (rule.enabler != null) {
710             out.attribute(null, RULE_ATT_ENABLER, rule.enabler);
711         }
712         if (rule.condition != null) {
713             writeConditionXml(rule.condition, out);
714         }
715         if (rule.zenPolicy != null) {
716             writeZenPolicyXml(rule.zenPolicy, out);
717         }
718         out.attribute(null, RULE_ATT_MODIFIED, Boolean.toString(rule.modified));
719     }
720 
readConditionXml(XmlPullParser parser)721     public static Condition readConditionXml(XmlPullParser parser) {
722         final Uri id = safeUri(parser, CONDITION_ATT_ID);
723         if (id == null) return null;
724         final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
725         final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
726         final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
727         final int icon = safeInt(parser, CONDITION_ATT_ICON, -1);
728         final int state = safeInt(parser, CONDITION_ATT_STATE, -1);
729         final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1);
730         try {
731             return new Condition(id, summary, line1, line2, icon, state, flags);
732         } catch (IllegalArgumentException e) {
733             Slog.w(TAG, "Unable to read condition xml", e);
734             return null;
735         }
736     }
737 
writeConditionXml(Condition c, XmlSerializer out)738     public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException {
739         out.attribute(null, CONDITION_ATT_ID, c.id.toString());
740         out.attribute(null, CONDITION_ATT_SUMMARY, c.summary);
741         out.attribute(null, CONDITION_ATT_LINE1, c.line1);
742         out.attribute(null, CONDITION_ATT_LINE2, c.line2);
743         out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon));
744         out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state));
745         out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags));
746     }
747 
748     /**
749      * Read the zen policy from xml
750      * Returns null if no zen policy exists
751      */
readZenPolicyXml(XmlPullParser parser)752     public static ZenPolicy readZenPolicyXml(XmlPullParser parser) {
753         boolean policySet = false;
754 
755         ZenPolicy.Builder builder = new ZenPolicy.Builder();
756         final int calls = safeInt(parser, ALLOW_ATT_CALLS_FROM, ZenPolicy.PEOPLE_TYPE_UNSET);
757         final int messages = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, ZenPolicy.PEOPLE_TYPE_UNSET);
758         final int repeatCallers = safeInt(parser, ALLOW_ATT_REPEAT_CALLERS, ZenPolicy.STATE_UNSET);
759         final int alarms = safeInt(parser, ALLOW_ATT_ALARMS, ZenPolicy.STATE_UNSET);
760         final int media = safeInt(parser, ALLOW_ATT_MEDIA, ZenPolicy.STATE_UNSET);
761         final int system = safeInt(parser, ALLOW_ATT_SYSTEM, ZenPolicy.STATE_UNSET);
762         final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET);
763         final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET);
764 
765         if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
766             builder.allowCalls(calls);
767             policySet = true;
768         }
769         if (messages != ZenPolicy.PEOPLE_TYPE_UNSET) {
770             builder.allowMessages(messages);
771             policySet = true;
772         }
773         if (repeatCallers != ZenPolicy.STATE_UNSET) {
774             builder.allowRepeatCallers(repeatCallers == ZenPolicy.STATE_ALLOW);
775             policySet = true;
776         }
777         if (alarms != ZenPolicy.STATE_UNSET) {
778             builder.allowAlarms(alarms == ZenPolicy.STATE_ALLOW);
779             policySet = true;
780         }
781         if (media != ZenPolicy.STATE_UNSET) {
782             builder.allowMedia(media == ZenPolicy.STATE_ALLOW);
783             policySet = true;
784         }
785         if (system != ZenPolicy.STATE_UNSET) {
786             builder.allowSystem(system == ZenPolicy.STATE_ALLOW);
787             policySet = true;
788         }
789         if (events != ZenPolicy.STATE_UNSET) {
790             builder.allowEvents(events == ZenPolicy.STATE_ALLOW);
791             policySet = true;
792         }
793         if (reminders != ZenPolicy.STATE_UNSET) {
794             builder.allowReminders(reminders == ZenPolicy.STATE_ALLOW);
795             policySet = true;
796         }
797 
798         final int fullScreenIntent = safeInt(parser, SHOW_ATT_FULL_SCREEN_INTENT,
799                 ZenPolicy.STATE_UNSET);
800         final int lights = safeInt(parser, SHOW_ATT_LIGHTS, ZenPolicy.STATE_UNSET);
801         final int peek = safeInt(parser, SHOW_ATT_PEEK, ZenPolicy.STATE_UNSET);
802         final int statusBar = safeInt(parser, SHOW_ATT_STATUS_BAR_ICONS, ZenPolicy.STATE_UNSET);
803         final int badges = safeInt(parser, SHOW_ATT_BADGES, ZenPolicy.STATE_UNSET);
804         final int ambient = safeInt(parser, SHOW_ATT_AMBIENT, ZenPolicy.STATE_UNSET);
805         final int notificationList = safeInt(parser, SHOW_ATT_NOTIFICATION_LIST,
806                 ZenPolicy.STATE_UNSET);
807 
808         if (fullScreenIntent != ZenPolicy.STATE_UNSET) {
809             builder.showFullScreenIntent(fullScreenIntent == ZenPolicy.STATE_ALLOW);
810             policySet = true;
811         }
812         if (lights != ZenPolicy.STATE_UNSET) {
813             builder.showLights(lights == ZenPolicy.STATE_ALLOW);
814             policySet = true;
815         }
816         if (peek != ZenPolicy.STATE_UNSET) {
817             builder.showPeeking(peek == ZenPolicy.STATE_ALLOW);
818             policySet = true;
819         }
820         if (statusBar != ZenPolicy.STATE_UNSET) {
821             builder.showStatusBarIcons(statusBar == ZenPolicy.STATE_ALLOW);
822             policySet = true;
823         }
824         if (badges != ZenPolicy.STATE_UNSET) {
825             builder.showBadges(badges == ZenPolicy.STATE_ALLOW);
826             policySet = true;
827         }
828         if (ambient != ZenPolicy.STATE_UNSET) {
829             builder.showInAmbientDisplay(ambient == ZenPolicy.STATE_ALLOW);
830             policySet = true;
831         }
832         if (notificationList != ZenPolicy.STATE_UNSET) {
833             builder.showInNotificationList(notificationList == ZenPolicy.STATE_ALLOW);
834             policySet = true;
835         }
836 
837         if (policySet) {
838             return builder.build();
839         }
840         return null;
841     }
842 
843     /**
844      * Writes zen policy to xml
845      */
writeZenPolicyXml(ZenPolicy policy, XmlSerializer out)846     public static void writeZenPolicyXml(ZenPolicy policy, XmlSerializer out)
847             throws IOException {
848         writeZenPolicyState(ALLOW_ATT_CALLS_FROM, policy.getPriorityCallSenders(), out);
849         writeZenPolicyState(ALLOW_ATT_MESSAGES_FROM, policy.getPriorityMessageSenders(), out);
850         writeZenPolicyState(ALLOW_ATT_REPEAT_CALLERS, policy.getPriorityCategoryRepeatCallers(),
851                 out);
852         writeZenPolicyState(ALLOW_ATT_ALARMS, policy.getPriorityCategoryAlarms(), out);
853         writeZenPolicyState(ALLOW_ATT_MEDIA, policy.getPriorityCategoryMedia(), out);
854         writeZenPolicyState(ALLOW_ATT_SYSTEM, policy.getPriorityCategorySystem(), out);
855         writeZenPolicyState(ALLOW_ATT_REMINDERS, policy.getPriorityCategoryReminders(), out);
856         writeZenPolicyState(ALLOW_ATT_EVENTS, policy.getPriorityCategoryEvents(), out);
857 
858         writeZenPolicyState(SHOW_ATT_FULL_SCREEN_INTENT, policy.getVisualEffectFullScreenIntent(),
859                 out);
860         writeZenPolicyState(SHOW_ATT_LIGHTS, policy.getVisualEffectLights(), out);
861         writeZenPolicyState(SHOW_ATT_PEEK, policy.getVisualEffectPeek(), out);
862         writeZenPolicyState(SHOW_ATT_STATUS_BAR_ICONS, policy.getVisualEffectStatusBar(), out);
863         writeZenPolicyState(SHOW_ATT_BADGES, policy.getVisualEffectBadge(), out);
864         writeZenPolicyState(SHOW_ATT_AMBIENT, policy.getVisualEffectAmbient(), out);
865         writeZenPolicyState(SHOW_ATT_NOTIFICATION_LIST, policy.getVisualEffectNotificationList(),
866                 out);
867     }
868 
writeZenPolicyState(String attr, int val, XmlSerializer out)869     private static void writeZenPolicyState(String attr, int val, XmlSerializer out)
870             throws IOException {
871         if (Objects.equals(attr, ALLOW_ATT_CALLS_FROM)
872                 || Objects.equals(attr, ALLOW_ATT_MESSAGES_FROM)) {
873             if (val != ZenPolicy.PEOPLE_TYPE_UNSET) {
874                 out.attribute(null, attr, Integer.toString(val));
875             }
876         } else {
877             if (val != ZenPolicy.STATE_UNSET) {
878                 out.attribute(null, attr, Integer.toString(val));
879             }
880         }
881     }
882 
isValidHour(int val)883     public static boolean isValidHour(int val) {
884         return val >= 0 && val < 24;
885     }
886 
isValidMinute(int val)887     public static boolean isValidMinute(int val) {
888         return val >= 0 && val < 60;
889     }
890 
isValidSource(int source)891     private static boolean isValidSource(int source) {
892         return source >= SOURCE_ANYONE && source <= MAX_SOURCE;
893     }
894 
unsafeBoolean(XmlPullParser parser, String att)895     private static Boolean unsafeBoolean(XmlPullParser parser, String att) {
896         final String val = parser.getAttributeValue(null, att);
897         if (TextUtils.isEmpty(val)) return null;
898         return Boolean.parseBoolean(val);
899     }
900 
safeBoolean(XmlPullParser parser, String att, boolean defValue)901     private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) {
902         final String val = parser.getAttributeValue(null, att);
903         return safeBoolean(val, defValue);
904     }
905 
safeBoolean(String val, boolean defValue)906     private static boolean safeBoolean(String val, boolean defValue) {
907         if (TextUtils.isEmpty(val)) return defValue;
908         return Boolean.parseBoolean(val);
909     }
910 
safeInt(XmlPullParser parser, String att, int defValue)911     private static int safeInt(XmlPullParser parser, String att, int defValue) {
912         final String val = parser.getAttributeValue(null, att);
913         return tryParseInt(val, defValue);
914     }
915 
safeComponentName(XmlPullParser parser, String att)916     private static ComponentName safeComponentName(XmlPullParser parser, String att) {
917         final String val = parser.getAttributeValue(null, att);
918         if (TextUtils.isEmpty(val)) return null;
919         return ComponentName.unflattenFromString(val);
920     }
921 
safeUri(XmlPullParser parser, String att)922     private static Uri safeUri(XmlPullParser parser, String att) {
923         final String val = parser.getAttributeValue(null, att);
924         if (TextUtils.isEmpty(val)) return null;
925         return Uri.parse(val);
926     }
927 
safeLong(XmlPullParser parser, String att, long defValue)928     private static long safeLong(XmlPullParser parser, String att, long defValue) {
929         final String val = parser.getAttributeValue(null, att);
930         return tryParseLong(val, defValue);
931     }
932 
933     @Override
describeContents()934     public int describeContents() {
935         return 0;
936     }
937 
copy()938     public ZenModeConfig copy() {
939         final Parcel parcel = Parcel.obtain();
940         try {
941             writeToParcel(parcel, 0);
942             parcel.setDataPosition(0);
943             return new ZenModeConfig(parcel);
944         } finally {
945             parcel.recycle();
946         }
947     }
948 
949     public static final @android.annotation.NonNull Parcelable.Creator<ZenModeConfig> CREATOR
950             = new Parcelable.Creator<ZenModeConfig>() {
951         @Override
952         public ZenModeConfig createFromParcel(Parcel source) {
953             return new ZenModeConfig(source);
954         }
955 
956         @Override
957         public ZenModeConfig[] newArray(int size) {
958             return new ZenModeConfig[size];
959         }
960     };
961 
962     /**
963      * Converts a ZenModeConfig to a ZenPolicy
964      */
toZenPolicy()965     public ZenPolicy toZenPolicy() {
966         ZenPolicy.Builder builder = new ZenPolicy.Builder()
967                 .allowCalls(allowCalls
968                         ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
969                         : ZenPolicy.PEOPLE_TYPE_NONE)
970                 .allowRepeatCallers(allowRepeatCallers)
971                 .allowMessages(allowMessages
972                         ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
973                         : ZenPolicy.PEOPLE_TYPE_NONE)
974                 .allowReminders(allowReminders)
975                 .allowEvents(allowEvents)
976                 .allowAlarms(allowAlarms)
977                 .allowMedia(allowMedia)
978                 .allowSystem(allowSystem)
979                 .allowConversations(allowConversations
980                         ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom)
981                         : ZenPolicy.PEOPLE_TYPE_NONE);
982         if (suppressedVisualEffects == 0) {
983             builder.showAllVisualEffects();
984         } else {
985             // configs don't have an unset state: wither true or false.
986             builder.showFullScreenIntent(
987                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0);
988             builder.showLights(
989                     (suppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) == 0);
990             builder.showPeeking(
991                     (suppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) == 0);
992             builder.showStatusBarIcons(
993                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_STATUS_BAR) == 0);
994             builder.showBadges(
995                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_BADGE) == 0);
996             builder.showInAmbientDisplay(
997                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_AMBIENT) == 0);
998             builder.showInNotificationList(
999                     (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0);
1000         }
1001         return builder.build();
1002     }
1003 
1004     /**
1005      * Converts a zenPolicy to a notificationPolicy using this ZenModeConfig's values as its
1006      * defaults for all unset values in zenPolicy
1007      */
toNotificationPolicy(ZenPolicy zenPolicy)1008     public Policy toNotificationPolicy(ZenPolicy zenPolicy) {
1009         NotificationManager.Policy defaultPolicy = toNotificationPolicy();
1010         int priorityCategories = 0;
1011         int suppressedVisualEffects = 0;
1012         int callSenders = defaultPolicy.priorityCallSenders;
1013         int messageSenders = defaultPolicy.priorityMessageSenders;
1014         int conversationSenders = defaultPolicy.priorityConversationSenders;
1015 
1016         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REMINDERS,
1017                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REMINDERS, defaultPolicy))) {
1018             priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
1019         }
1020 
1021         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_EVENTS,
1022                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_EVENTS, defaultPolicy))) {
1023             priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
1024         }
1025 
1026         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
1027                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
1028             priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
1029             messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
1030                     messageSenders);
1031         }
1032 
1033         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
1034                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
1035             priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
1036             conversationSenders = getNotificationPolicySenders(
1037                     zenPolicy.getPriorityConversationSenders(),
1038                     conversationSenders);
1039         }
1040 
1041         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
1042                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
1043             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
1044             callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
1045                     callSenders);
1046         }
1047 
1048         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
1049                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS,
1050                         defaultPolicy))) {
1051             priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
1052         }
1053 
1054         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_ALARMS,
1055                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_ALARMS, defaultPolicy))) {
1056             priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
1057         }
1058 
1059         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MEDIA,
1060                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MEDIA, defaultPolicy))) {
1061             priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
1062         }
1063 
1064         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_SYSTEM,
1065                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_SYSTEM, defaultPolicy))) {
1066             priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
1067         }
1068 
1069         boolean suppressFullScreenIntent = !zenPolicy.isVisualEffectAllowed(
1070                 ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT,
1071                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT,
1072                         defaultPolicy));
1073 
1074         boolean suppressLights = !zenPolicy.isVisualEffectAllowed(
1075                 ZenPolicy.VISUAL_EFFECT_LIGHTS,
1076                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_LIGHTS,
1077                         defaultPolicy));
1078 
1079         boolean suppressAmbient = !zenPolicy.isVisualEffectAllowed(
1080                 ZenPolicy.VISUAL_EFFECT_AMBIENT,
1081                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_AMBIENT,
1082                         defaultPolicy));
1083 
1084         if (suppressFullScreenIntent && suppressLights && suppressAmbient) {
1085             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
1086         }
1087 
1088         if (suppressFullScreenIntent) {
1089             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
1090         }
1091 
1092         if (suppressLights) {
1093             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
1094         }
1095 
1096         if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_PEEK,
1097                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_PEEK,
1098                         defaultPolicy))) {
1099             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK;
1100             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
1101         }
1102 
1103         if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_STATUS_BAR,
1104                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_STATUS_BAR,
1105                         defaultPolicy))) {
1106             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_STATUS_BAR;
1107         }
1108 
1109         if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_BADGE,
1110                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_BADGE,
1111                         defaultPolicy))) {
1112             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
1113         }
1114 
1115         if (suppressAmbient) {
1116             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
1117         }
1118 
1119         if (!zenPolicy.isVisualEffectAllowed(ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST,
1120                 isVisualEffectAllowed(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST,
1121                         defaultPolicy))) {
1122             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
1123         }
1124 
1125         return new NotificationManager.Policy(priorityCategories, callSenders,
1126                 messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders);
1127     }
1128 
isPriorityCategoryEnabled(int categoryType, Policy policy)1129     private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) {
1130         return (policy.priorityCategories & categoryType) != 0;
1131     }
1132 
isVisualEffectAllowed(int visualEffect, Policy policy)1133     private boolean isVisualEffectAllowed(int visualEffect, Policy policy) {
1134         return (policy.suppressedVisualEffects & visualEffect) == 0;
1135     }
1136 
getNotificationPolicySenders(@enPolicy.PeopleType int senders, int defaultPolicySender)1137     private int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
1138             int defaultPolicySender) {
1139         switch (senders) {
1140             case ZenPolicy.PEOPLE_TYPE_ANYONE:
1141                 return Policy.PRIORITY_SENDERS_ANY;
1142             case ZenPolicy.PEOPLE_TYPE_CONTACTS:
1143                 return Policy.PRIORITY_SENDERS_CONTACTS;
1144             case ZenPolicy.PEOPLE_TYPE_STARRED:
1145                 return Policy.PRIORITY_SENDERS_STARRED;
1146             default:
1147                 return defaultPolicySender;
1148         }
1149     }
1150 
1151 
1152     /**
1153      * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
1154      */
getZenPolicySenders(int senders)1155     public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
1156         switch (senders) {
1157             case Policy.PRIORITY_SENDERS_ANY:
1158                 return ZenPolicy.PEOPLE_TYPE_ANYONE;
1159             case Policy.PRIORITY_SENDERS_CONTACTS:
1160                 return ZenPolicy.PEOPLE_TYPE_CONTACTS;
1161             case Policy.PRIORITY_SENDERS_STARRED:
1162             default:
1163                 return ZenPolicy.PEOPLE_TYPE_STARRED;
1164         }
1165     }
1166 
toNotificationPolicy()1167     public Policy toNotificationPolicy() {
1168         int priorityCategories = 0;
1169         int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
1170         int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS;
1171         int priorityConversationSenders = Policy.CONVERSATION_SENDERS_IMPORTANT;
1172         if (allowConversations) {
1173             priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
1174         }
1175         if (allowCalls) {
1176             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
1177         }
1178         if (allowMessages) {
1179             priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
1180         }
1181         if (allowEvents) {
1182             priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
1183         }
1184         if (allowReminders) {
1185             priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
1186         }
1187         if (allowRepeatCallers) {
1188             priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
1189         }
1190         if (allowAlarms) {
1191             priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
1192         }
1193         if (allowMedia) {
1194             priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA;
1195         }
1196         if (allowSystem) {
1197             priorityCategories |= Policy.PRIORITY_CATEGORY_SYSTEM;
1198         }
1199         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
1200         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
1201         priorityConversationSenders = allowConversationsFrom;
1202 
1203         return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
1204                 suppressedVisualEffects, areChannelsBypassingDnd
1205                 ? Policy.STATE_CHANNELS_BYPASSING_DND : 0,
1206                 priorityConversationSenders);
1207     }
1208 
1209     /**
1210      * Creates scheduleCalendar from a condition id
1211      * @param conditionId
1212      * @return ScheduleCalendar with info populated with conditionId
1213      */
toScheduleCalendar(Uri conditionId)1214     public static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
1215         final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
1216         if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
1217         final ScheduleCalendar sc = new ScheduleCalendar();
1218         sc.setSchedule(schedule);
1219         sc.setTimeZone(TimeZone.getDefault());
1220         return sc;
1221     }
1222 
sourceToPrioritySenders(int source, int def)1223     private static int sourceToPrioritySenders(int source, int def) {
1224         switch (source) {
1225             case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY;
1226             case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS;
1227             case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED;
1228             default: return def;
1229         }
1230     }
1231 
prioritySendersToSource(int prioritySenders, int def)1232     private static int prioritySendersToSource(int prioritySenders, int def) {
1233         switch (prioritySenders) {
1234             case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
1235             case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
1236             case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
1237             default: return def;
1238         }
1239     }
1240 
normalizePrioritySenders(int prioritySenders, int def)1241     private static int normalizePrioritySenders(int prioritySenders, int def) {
1242         if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
1243                 || prioritySenders == Policy.PRIORITY_SENDERS_STARRED
1244                 || prioritySenders == Policy.PRIORITY_SENDERS_ANY)) {
1245             return def;
1246         }
1247         return prioritySenders;
1248     }
1249 
normalizeConversationSenders(boolean allowed, int senders, int def)1250     private static int normalizeConversationSenders(boolean allowed, int senders, int def) {
1251         if (!allowed) {
1252             return CONVERSATION_SENDERS_NONE;
1253         }
1254         if (!(senders == CONVERSATION_SENDERS_ANYONE
1255                 || senders == CONVERSATION_SENDERS_IMPORTANT
1256                 || senders == CONVERSATION_SENDERS_NONE)) {
1257             return def;
1258         }
1259         return senders;
1260     }
1261 
applyNotificationPolicy(Policy policy)1262     public void applyNotificationPolicy(Policy policy) {
1263         if (policy == null) return;
1264         allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
1265         allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
1266         allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
1267         allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
1268         allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
1269         allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
1270         allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
1271         allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
1272                 != 0;
1273         allowCallsFrom = normalizePrioritySenders(policy.priorityCallSenders, allowCallsFrom);
1274         allowMessagesFrom = normalizePrioritySenders(policy.priorityMessageSenders,
1275                 allowMessagesFrom);
1276         if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
1277             suppressedVisualEffects = policy.suppressedVisualEffects;
1278         }
1279         allowConversations = (policy.priorityCategories
1280                 & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
1281         allowConversationsFrom = normalizeConversationSenders(allowConversations,
1282                 policy.priorityConversationSenders,
1283                 allowConversationsFrom);
1284         if (policy.state != Policy.STATE_UNSET) {
1285             areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
1286         }
1287     }
1288 
toTimeCondition(Context context, int minutesFromNow, int userHandle)1289     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
1290         return toTimeCondition(context, minutesFromNow, userHandle, false /*shortVersion*/);
1291     }
1292 
toTimeCondition(Context context, int minutesFromNow, int userHandle, boolean shortVersion)1293     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle,
1294             boolean shortVersion) {
1295         final long now = System.currentTimeMillis();
1296         final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
1297         return toTimeCondition(context, now + millis, minutesFromNow, userHandle, shortVersion);
1298     }
1299 
toTimeCondition(Context context, long time, int minutes, int userHandle, boolean shortVersion)1300     public static Condition toTimeCondition(Context context, long time, int minutes,
1301             int userHandle, boolean shortVersion) {
1302         final int num;
1303         String summary, line1, line2;
1304         final CharSequence formattedTime =
1305                 getFormattedTime(context, time, isToday(time), userHandle);
1306         final Resources res = context.getResources();
1307         if (minutes < 60) {
1308             // display as minutes
1309             num = minutes;
1310             int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
1311                     : R.plurals.zen_mode_duration_minutes_summary;
1312             summary = res.getQuantityString(summaryResId, num, num, formattedTime);
1313             int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
1314                     : R.plurals.zen_mode_duration_minutes;
1315             line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
1316             line2 = res.getString(R.string.zen_mode_until, formattedTime);
1317         } else if (minutes < DAY_MINUTES) {
1318             // display as hours
1319             num =  Math.round(minutes / 60f);
1320             int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
1321                     : R.plurals.zen_mode_duration_hours_summary;
1322             summary = res.getQuantityString(summaryResId, num, num, formattedTime);
1323             int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
1324                     : R.plurals.zen_mode_duration_hours;
1325             line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
1326             line2 = res.getString(R.string.zen_mode_until, formattedTime);
1327         } else {
1328             // display as day/time
1329             summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
1330         }
1331         final Uri id = toCountdownConditionId(time, false);
1332         return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
1333                 Condition.FLAG_RELEVANT_NOW);
1334     }
1335 
1336     /**
1337      * Converts countdown to alarm parameters into a condition with user facing summary
1338      */
toNextAlarmCondition(Context context, long alarm, int userHandle)1339     public static Condition toNextAlarmCondition(Context context, long alarm,
1340             int userHandle) {
1341         boolean isSameDay = isToday(alarm);
1342         final CharSequence formattedTime = getFormattedTime(context, alarm, isSameDay, userHandle);
1343         final Resources res = context.getResources();
1344         final String line1 = res.getString(R.string.zen_mode_until, formattedTime);
1345         final Uri id = toCountdownConditionId(alarm, true);
1346         return new Condition(id, "", line1, "", 0, Condition.STATE_TRUE,
1347                 Condition.FLAG_RELEVANT_NOW);
1348     }
1349 
1350     /**
1351      * Creates readable time from time in milliseconds
1352      */
getFormattedTime(Context context, long time, boolean isSameDay, int userHandle)1353     public static CharSequence getFormattedTime(Context context, long time, boolean isSameDay,
1354             int userHandle) {
1355         String skeleton = (!isSameDay ? "EEE " : "")
1356                 + (DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma");
1357         final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
1358         return DateFormat.format(pattern, time);
1359     }
1360 
1361     /**
1362      * Determines whether a time in milliseconds is today or not
1363      */
isToday(long time)1364     public static boolean isToday(long time) {
1365         GregorianCalendar now = new GregorianCalendar();
1366         GregorianCalendar endTime = new GregorianCalendar();
1367         endTime.setTimeInMillis(time);
1368         if (now.get(Calendar.YEAR) == endTime.get(Calendar.YEAR)
1369                 && now.get(Calendar.MONTH) == endTime.get(Calendar.MONTH)
1370                 && now.get(Calendar.DATE) == endTime.get(Calendar.DATE)) {
1371             return true;
1372         }
1373         return false;
1374     }
1375 
1376     // ==== Built-in system conditions ====
1377 
1378     public static final String SYSTEM_AUTHORITY = "android";
1379 
1380     // ==== Built-in system condition: countdown ====
1381 
1382     public static final String COUNTDOWN_PATH = "countdown";
1383 
1384     public static final String IS_ALARM_PATH = "alarm";
1385 
1386     /**
1387      * Converts countdown condition parameters into a condition id.
1388      */
toCountdownConditionId(long time, boolean alarm)1389     public static Uri toCountdownConditionId(long time, boolean alarm) {
1390         return new Uri.Builder().scheme(Condition.SCHEME)
1391                 .authority(SYSTEM_AUTHORITY)
1392                 .appendPath(COUNTDOWN_PATH)
1393                 .appendPath(Long.toString(time))
1394                 .appendPath(IS_ALARM_PATH)
1395                 .appendPath(Boolean.toString(alarm))
1396                 .build();
1397     }
1398 
tryParseCountdownConditionId(Uri conditionId)1399     public static long tryParseCountdownConditionId(Uri conditionId) {
1400         if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0;
1401         if (conditionId.getPathSegments().size() < 2
1402                 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0;
1403         try {
1404             return Long.parseLong(conditionId.getPathSegments().get(1));
1405         } catch (RuntimeException e) {
1406             Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e);
1407             return 0;
1408         }
1409     }
1410 
1411     /**
1412      * Returns whether this condition is a countdown condition.
1413      */
isValidCountdownConditionId(Uri conditionId)1414     public static boolean isValidCountdownConditionId(Uri conditionId) {
1415         return tryParseCountdownConditionId(conditionId) != 0;
1416     }
1417 
1418     /**
1419      * Returns whether this condition is a countdown to an alarm.
1420      */
isValidCountdownToAlarmConditionId(Uri conditionId)1421     public static boolean isValidCountdownToAlarmConditionId(Uri conditionId) {
1422         if (tryParseCountdownConditionId(conditionId) != 0) {
1423             if (conditionId.getPathSegments().size() < 4
1424                     || !IS_ALARM_PATH.equals(conditionId.getPathSegments().get(2))) {
1425                 return false;
1426             }
1427             try {
1428                 return Boolean.parseBoolean(conditionId.getPathSegments().get(3));
1429             } catch (RuntimeException e) {
1430                 Slog.w(TAG, "Error parsing countdown alarm condition: " + conditionId, e);
1431                 return false;
1432             }
1433         }
1434         return false;
1435     }
1436 
1437     // ==== Built-in system condition: schedule ====
1438 
1439     public static final String SCHEDULE_PATH = "schedule";
1440 
toScheduleConditionId(ScheduleInfo schedule)1441     public static Uri toScheduleConditionId(ScheduleInfo schedule) {
1442         return new Uri.Builder().scheme(Condition.SCHEME)
1443                 .authority(SYSTEM_AUTHORITY)
1444                 .appendPath(SCHEDULE_PATH)
1445                 .appendQueryParameter("days", toDayList(schedule.days))
1446                 .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
1447                 .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
1448                 .appendQueryParameter("exitAtAlarm", String.valueOf(schedule.exitAtAlarm))
1449                 .build();
1450     }
1451 
isValidScheduleConditionId(Uri conditionId)1452     public static boolean isValidScheduleConditionId(Uri conditionId) {
1453         ScheduleInfo info;
1454         try {
1455             info = tryParseScheduleConditionId(conditionId);
1456         } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
1457             return false;
1458         }
1459 
1460         if (info == null || info.days == null || info.days.length == 0) {
1461             return false;
1462         }
1463         return true;
1464     }
1465 
1466     /**
1467      * Returns whether the conditionId is a valid ScheduleCondition.
1468      * If allowNever is true, this will return true even if the ScheduleCondition never occurs.
1469      */
isValidScheduleConditionId(Uri conditionId, boolean allowNever)1470     public static boolean isValidScheduleConditionId(Uri conditionId, boolean allowNever) {
1471         ScheduleInfo info;
1472         try {
1473             info = tryParseScheduleConditionId(conditionId);
1474         } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
1475             return false;
1476         }
1477 
1478         if (info == null || (!allowNever && (info.days == null || info.days.length == 0))) {
1479             return false;
1480         }
1481         return true;
1482     }
1483 
1484     @UnsupportedAppUsage
tryParseScheduleConditionId(Uri conditionId)1485     public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
1486         final boolean isSchedule =  conditionId != null
1487                 && Condition.SCHEME.equals(conditionId.getScheme())
1488                 && ZenModeConfig.SYSTEM_AUTHORITY.equals(conditionId.getAuthority())
1489                 && conditionId.getPathSegments().size() == 1
1490                 && ZenModeConfig.SCHEDULE_PATH.equals(conditionId.getPathSegments().get(0));
1491         if (!isSchedule) return null;
1492         final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
1493         final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
1494         if (start == null || end == null) return null;
1495         final ScheduleInfo rt = new ScheduleInfo();
1496         rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
1497         rt.startHour = start[0];
1498         rt.startMinute = start[1];
1499         rt.endHour = end[0];
1500         rt.endMinute = end[1];
1501         rt.exitAtAlarm = safeBoolean(conditionId.getQueryParameter("exitAtAlarm"), false);
1502         return rt;
1503     }
1504 
getScheduleConditionProvider()1505     public static ComponentName getScheduleConditionProvider() {
1506         return new ComponentName(SYSTEM_AUTHORITY, "ScheduleConditionProvider");
1507     }
1508 
1509     public static class ScheduleInfo {
1510         @UnsupportedAppUsage
1511         public int[] days;
1512         @UnsupportedAppUsage
1513         public int startHour;
1514         @UnsupportedAppUsage
1515         public int startMinute;
1516         @UnsupportedAppUsage
1517         public int endHour;
1518         @UnsupportedAppUsage
1519         public int endMinute;
1520         public boolean exitAtAlarm;
1521         public long nextAlarm;
1522 
1523         @Override
hashCode()1524         public int hashCode() {
1525             return 0;
1526         }
1527 
1528         @Override
equals(Object o)1529         public boolean equals(Object o) {
1530             if (!(o instanceof ScheduleInfo)) return false;
1531             final ScheduleInfo other = (ScheduleInfo) o;
1532             return toDayList(days).equals(toDayList(other.days))
1533                     && startHour == other.startHour
1534                     && startMinute == other.startMinute
1535                     && endHour == other.endHour
1536                     && endMinute == other.endMinute
1537                     && exitAtAlarm == other.exitAtAlarm;
1538         }
1539 
copy()1540         public ScheduleInfo copy() {
1541             final ScheduleInfo rt = new ScheduleInfo();
1542             if (days != null) {
1543                 rt.days = new int[days.length];
1544                 System.arraycopy(days, 0, rt.days, 0, days.length);
1545             }
1546             rt.startHour = startHour;
1547             rt.startMinute = startMinute;
1548             rt.endHour = endHour;
1549             rt.endMinute = endMinute;
1550             rt.exitAtAlarm = exitAtAlarm;
1551             rt.nextAlarm = nextAlarm;
1552             return rt;
1553         }
1554 
1555         @Override
toString()1556         public String toString() {
1557             return "ScheduleInfo{" +
1558                     "days=" + Arrays.toString(days) +
1559                     ", startHour=" + startHour +
1560                     ", startMinute=" + startMinute +
1561                     ", endHour=" + endHour +
1562                     ", endMinute=" + endMinute +
1563                     ", exitAtAlarm=" + exitAtAlarm +
1564                     ", nextAlarm=" + ts(nextAlarm) +
1565                     '}';
1566         }
1567 
ts(long time)1568         protected static String ts(long time) {
1569             return new Date(time) + " (" + time + ")";
1570         }
1571     }
1572 
1573     // ==== Built-in system condition: event ====
1574 
1575     public static final String EVENT_PATH = "event";
1576 
toEventConditionId(EventInfo event)1577     public static Uri toEventConditionId(EventInfo event) {
1578         return new Uri.Builder().scheme(Condition.SCHEME)
1579                 .authority(SYSTEM_AUTHORITY)
1580                 .appendPath(EVENT_PATH)
1581                 .appendQueryParameter("userId", Long.toString(event.userId))
1582                 .appendQueryParameter("calendar", event.calName != null ? event.calName : "")
1583                 .appendQueryParameter("calendarId", event.calendarId != null
1584                         ? event.calendarId.toString() : "")
1585                 .appendQueryParameter("reply", Integer.toString(event.reply))
1586                 .build();
1587     }
1588 
isValidEventConditionId(Uri conditionId)1589     public static boolean isValidEventConditionId(Uri conditionId) {
1590         return tryParseEventConditionId(conditionId) != null;
1591     }
1592 
tryParseEventConditionId(Uri conditionId)1593     public static EventInfo tryParseEventConditionId(Uri conditionId) {
1594         final boolean isEvent = conditionId != null
1595                 && Condition.SCHEME.equals(conditionId.getScheme())
1596                 && ZenModeConfig.SYSTEM_AUTHORITY.equals(conditionId.getAuthority())
1597                 && conditionId.getPathSegments().size() == 1
1598                 && EVENT_PATH.equals(conditionId.getPathSegments().get(0));
1599         if (!isEvent) return null;
1600         final EventInfo rt = new EventInfo();
1601         rt.userId = tryParseInt(conditionId.getQueryParameter("userId"), UserHandle.USER_NULL);
1602         rt.calName = conditionId.getQueryParameter("calendar");
1603         if (TextUtils.isEmpty(rt.calName)) {
1604             rt.calName = null;
1605         }
1606         rt.calendarId = tryParseLong(conditionId.getQueryParameter("calendarId"), null);
1607         rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0);
1608         return rt;
1609     }
1610 
getEventConditionProvider()1611     public static ComponentName getEventConditionProvider() {
1612         return new ComponentName(SYSTEM_AUTHORITY, "EventConditionProvider");
1613     }
1614 
1615     public static class EventInfo {
1616         public static final int REPLY_ANY_EXCEPT_NO = 0;
1617         public static final int REPLY_YES_OR_MAYBE = 1;
1618         public static final int REPLY_YES = 2;
1619 
1620         public int userId = UserHandle.USER_NULL;  // USER_NULL = unspecified - use current user
1621         public String calName;  // CalendarContract.Calendars.DISPLAY_NAME, or null for any
1622         public Long calendarId; // Calendars._ID, or null if restored from < Q calendar
1623         public int reply;
1624 
1625         @Override
hashCode()1626         public int hashCode() {
1627             return Objects.hash(userId, calName, calendarId, reply);
1628         }
1629 
1630         @Override
equals(Object o)1631         public boolean equals(Object o) {
1632             if (!(o instanceof EventInfo)) return false;
1633             final EventInfo other = (EventInfo) o;
1634             return userId == other.userId
1635                     && Objects.equals(calName, other.calName)
1636                     && reply == other.reply
1637                     && Objects.equals(calendarId, other.calendarId);
1638         }
1639 
copy()1640         public EventInfo copy() {
1641             final EventInfo rt = new EventInfo();
1642             rt.userId = userId;
1643             rt.calName = calName;
1644             rt.reply = reply;
1645             rt.calendarId = calendarId;
1646             return rt;
1647         }
1648 
resolveUserId(int userId)1649         public static int resolveUserId(int userId) {
1650             return userId == UserHandle.USER_NULL ? ActivityManager.getCurrentUser() : userId;
1651         }
1652     }
1653 
1654     // ==== End built-in system conditions ====
1655 
tryParseHourAndMinute(String value)1656     private static int[] tryParseHourAndMinute(String value) {
1657         if (TextUtils.isEmpty(value)) return null;
1658         final int i = value.indexOf('.');
1659         if (i < 1 || i >= value.length() - 1) return null;
1660         final int hour = tryParseInt(value.substring(0, i), -1);
1661         final int minute = tryParseInt(value.substring(i + 1), -1);
1662         return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
1663     }
1664 
tryParseZenMode(String value, int defValue)1665     private static int tryParseZenMode(String value, int defValue) {
1666         final int rt = tryParseInt(value, defValue);
1667         return Global.isValidZenMode(rt) ? rt : defValue;
1668     }
1669 
newRuleId()1670     public static String newRuleId() {
1671         return UUID.randomUUID().toString().replace("-", "");
1672     }
1673 
1674     /**
1675      * Gets the name of the app associated with owner
1676      */
getOwnerCaption(Context context, String owner)1677     public static String getOwnerCaption(Context context, String owner) {
1678         final PackageManager pm = context.getPackageManager();
1679         try {
1680             final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
1681             if (info != null) {
1682                 final CharSequence seq = info.loadLabel(pm);
1683                 if (seq != null) {
1684                     final String str = seq.toString().trim();
1685                     if (str.length() > 0) {
1686                         return str;
1687                     }
1688                 }
1689             }
1690         } catch (Throwable e) {
1691             Slog.w(TAG, "Error loading owner caption", e);
1692         }
1693         return "";
1694     }
1695 
getConditionSummary(Context context, ZenModeConfig config, int userHandle, boolean shortVersion)1696     public static String getConditionSummary(Context context, ZenModeConfig config,
1697             int userHandle, boolean shortVersion) {
1698         return getConditionLine(context, config, userHandle, false /*useLine1*/, shortVersion);
1699     }
1700 
getConditionLine(Context context, ZenModeConfig config, int userHandle, boolean useLine1, boolean shortVersion)1701     private static String getConditionLine(Context context, ZenModeConfig config,
1702             int userHandle, boolean useLine1, boolean shortVersion) {
1703         if (config == null) return "";
1704         String summary = "";
1705         if (config.manualRule != null) {
1706             final Uri id = config.manualRule.conditionId;
1707             if (config.manualRule.enabler != null) {
1708                 summary = getOwnerCaption(context, config.manualRule.enabler);
1709             } else {
1710                 if (id == null) {
1711                     summary = context.getString(com.android.internal.R.string.zen_mode_forever);
1712                 } else {
1713                     final long time = tryParseCountdownConditionId(id);
1714                     Condition c = config.manualRule.condition;
1715                     if (time > 0) {
1716                         final long now = System.currentTimeMillis();
1717                         final long span = time - now;
1718                         c = toTimeCondition(context, time, Math.round(span / (float) MINUTES_MS),
1719                                 userHandle, shortVersion);
1720                     }
1721                     final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
1722                     summary = TextUtils.isEmpty(rt) ? "" : rt;
1723                 }
1724             }
1725         }
1726         for (ZenRule automaticRule : config.automaticRules.values()) {
1727             if (automaticRule.isAutomaticActive()) {
1728                 if (summary.isEmpty()) {
1729                     summary = automaticRule.name;
1730                 } else {
1731                     summary = context.getResources()
1732                             .getString(R.string.zen_mode_rule_name_combination, summary,
1733                                     automaticRule.name);
1734                 }
1735 
1736             }
1737         }
1738         return summary;
1739     }
1740 
1741     public static class ZenRule implements Parcelable {
1742         @UnsupportedAppUsage
1743         public boolean enabled;
1744         @UnsupportedAppUsage
1745         public boolean snoozing;         // user manually disabled this instance
1746         @UnsupportedAppUsage
1747         public String name;              // required for automatic
1748         @UnsupportedAppUsage
1749         public int zenMode;             // ie: Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
1750         @UnsupportedAppUsage
1751         public Uri conditionId;          // required for automatic
1752         public Condition condition;      // optional
1753         public ComponentName component;  // optional
1754         public ComponentName configurationActivity; // optional
1755         public String id;                // required for automatic (unique)
1756         @UnsupportedAppUsage
1757         public long creationTime;        // required for automatic
1758         // package name, only used for manual rules when they have turned DND on.
1759         public String enabler;
1760         public ZenPolicy zenPolicy;
1761         public boolean modified;    // rule has been modified from initial creation
1762         public String pkg;
1763 
ZenRule()1764         public ZenRule() { }
1765 
ZenRule(Parcel source)1766         public ZenRule(Parcel source) {
1767             enabled = source.readInt() == 1;
1768             snoozing = source.readInt() == 1;
1769             if (source.readInt() == 1) {
1770                 name = source.readString();
1771             }
1772             zenMode = source.readInt();
1773             conditionId = source.readParcelable(null);
1774             condition = source.readParcelable(null);
1775             component = source.readParcelable(null);
1776             configurationActivity = source.readParcelable(null);
1777             if (source.readInt() == 1) {
1778                 id = source.readString();
1779             }
1780             creationTime = source.readLong();
1781             if (source.readInt() == 1) {
1782                 enabler = source.readString();
1783             }
1784             zenPolicy = source.readParcelable(null);
1785             modified = source.readInt() == 1;
1786             pkg = source.readString();
1787         }
1788 
1789         @Override
describeContents()1790         public int describeContents() {
1791             return 0;
1792         }
1793 
1794         @Override
writeToParcel(Parcel dest, int flags)1795         public void writeToParcel(Parcel dest, int flags) {
1796             dest.writeInt(enabled ? 1 : 0);
1797             dest.writeInt(snoozing ? 1 : 0);
1798             if (name != null) {
1799                 dest.writeInt(1);
1800                 dest.writeString(name);
1801             } else {
1802                 dest.writeInt(0);
1803             }
1804             dest.writeInt(zenMode);
1805             dest.writeParcelable(conditionId, 0);
1806             dest.writeParcelable(condition, 0);
1807             dest.writeParcelable(component, 0);
1808             dest.writeParcelable(configurationActivity, 0);
1809             if (id != null) {
1810                 dest.writeInt(1);
1811                 dest.writeString(id);
1812             } else {
1813                 dest.writeInt(0);
1814             }
1815             dest.writeLong(creationTime);
1816             if (enabler != null) {
1817                 dest.writeInt(1);
1818                 dest.writeString(enabler);
1819             } else {
1820                 dest.writeInt(0);
1821             }
1822             dest.writeParcelable(zenPolicy, 0);
1823             dest.writeInt(modified ? 1 : 0);
1824             dest.writeString(pkg);
1825         }
1826 
1827         @Override
toString()1828         public String toString() {
1829             return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
1830                     .append("id=").append(id)
1831                     .append(",enabled=").append(String.valueOf(enabled).toUpperCase())
1832                     .append(",snoozing=").append(snoozing)
1833                     .append(",name=").append(name)
1834                     .append(",zenMode=").append(Global.zenModeToString(zenMode))
1835                     .append(",conditionId=").append(conditionId)
1836                     .append(",condition=").append(condition)
1837                     .append(",pkg=").append(pkg)
1838                     .append(",component=").append(component)
1839                     .append(",configActivity=").append(configurationActivity)
1840                     .append(",creationTime=").append(creationTime)
1841                     .append(",enabler=").append(enabler)
1842                     .append(",zenPolicy=").append(zenPolicy)
1843                     .append(",modified=").append(modified)
1844                     .append(']').toString();
1845         }
1846 
1847         /** @hide */
1848         // TODO: add configuration activity
dumpDebug(ProtoOutputStream proto, long fieldId)1849         public void dumpDebug(ProtoOutputStream proto, long fieldId) {
1850             final long token = proto.start(fieldId);
1851 
1852             proto.write(ZenRuleProto.ID, id);
1853             proto.write(ZenRuleProto.NAME, name);
1854             proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime);
1855             proto.write(ZenRuleProto.ENABLED, enabled);
1856             proto.write(ZenRuleProto.ENABLER, enabler);
1857             proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
1858             proto.write(ZenRuleProto.ZEN_MODE, zenMode);
1859             if (conditionId != null) {
1860                 proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString());
1861             }
1862             if (condition != null) {
1863                 condition.dumpDebug(proto, ZenRuleProto.CONDITION);
1864             }
1865             if (component != null) {
1866                 component.dumpDebug(proto, ZenRuleProto.COMPONENT);
1867             }
1868             if (zenPolicy != null) {
1869                 zenPolicy.dumpDebug(proto, ZenRuleProto.ZEN_POLICY);
1870             }
1871             proto.write(ZenRuleProto.MODIFIED, modified);
1872             proto.end(token);
1873         }
1874 
appendDiff(Diff d, String item, ZenRule from, ZenRule to)1875         private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
1876             if (d == null) return;
1877             if (from == null) {
1878                 if (to != null) {
1879                     d.addLine(item, "insert");
1880                 }
1881                 return;
1882             }
1883             from.appendDiff(d, item, to);
1884         }
1885 
appendDiff(Diff d, String item, ZenRule to)1886         private void appendDiff(Diff d, String item, ZenRule to) {
1887             if (to == null) {
1888                 d.addLine(item, "delete");
1889                 return;
1890             }
1891             if (enabled != to.enabled) {
1892                 d.addLine(item, "enabled", enabled, to.enabled);
1893             }
1894             if (snoozing != to.snoozing) {
1895                 d.addLine(item, "snoozing", snoozing, to.snoozing);
1896             }
1897             if (!Objects.equals(name, to.name)) {
1898                 d.addLine(item, "name", name, to.name);
1899             }
1900             if (zenMode != to.zenMode) {
1901                 d.addLine(item, "zenMode", zenMode, to.zenMode);
1902             }
1903             if (!Objects.equals(conditionId, to.conditionId)) {
1904                 d.addLine(item, "conditionId", conditionId, to.conditionId);
1905             }
1906             if (!Objects.equals(condition, to.condition)) {
1907                 d.addLine(item, "condition", condition, to.condition);
1908             }
1909             if (!Objects.equals(component, to.component)) {
1910                 d.addLine(item, "component", component, to.component);
1911             }
1912             if (!Objects.equals(configurationActivity, to.configurationActivity)) {
1913                 d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
1914             }
1915             if (!Objects.equals(id, to.id)) {
1916                 d.addLine(item, "id", id, to.id);
1917             }
1918             if (creationTime != to.creationTime) {
1919                 d.addLine(item, "creationTime", creationTime, to.creationTime);
1920             }
1921             if (!Objects.equals(enabler, to.enabler)) {
1922                 d.addLine(item, "enabler", enabler, to.enabler);
1923             }
1924             if (!Objects.equals(zenPolicy, to.zenPolicy)) {
1925                 d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
1926             }
1927             if (modified != to.modified) {
1928                 d.addLine(item, "modified", modified, to.modified);
1929             }
1930             if (!Objects.equals(pkg, to.pkg)) {
1931                 d.addLine(item, "pkg", pkg, to.pkg);
1932             }
1933         }
1934 
1935         @Override
equals(Object o)1936         public boolean equals(Object o) {
1937             if (!(o instanceof ZenRule)) return false;
1938             if (o == this) return true;
1939             final ZenRule other = (ZenRule) o;
1940             return other.enabled == enabled
1941                     && other.snoozing == snoozing
1942                     && Objects.equals(other.name, name)
1943                     && other.zenMode == zenMode
1944                     && Objects.equals(other.conditionId, conditionId)
1945                     && Objects.equals(other.condition, condition)
1946                     && Objects.equals(other.component, component)
1947                     && Objects.equals(other.configurationActivity, configurationActivity)
1948                     && Objects.equals(other.id, id)
1949                     && Objects.equals(other.enabler, enabler)
1950                     && Objects.equals(other.zenPolicy, zenPolicy)
1951                     && Objects.equals(other.pkg, pkg)
1952                     && other.modified == modified;
1953         }
1954 
1955         @Override
hashCode()1956         public int hashCode() {
1957             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
1958                     component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
1959         }
1960 
isAutomaticActive()1961         public boolean isAutomaticActive() {
1962             return enabled && !snoozing && pkg != null && isTrueOrUnknown();
1963         }
1964 
isTrueOrUnknown()1965         public boolean isTrueOrUnknown() {
1966             return condition != null && (condition.state == Condition.STATE_TRUE
1967                     || condition.state == Condition.STATE_UNKNOWN);
1968         }
1969 
1970         public static final @android.annotation.NonNull Parcelable.Creator<ZenRule> CREATOR
1971                 = new Parcelable.Creator<ZenRule>() {
1972             @Override
1973             public ZenRule createFromParcel(Parcel source) {
1974                 return new ZenRule(source);
1975             }
1976             @Override
1977             public ZenRule[] newArray(int size) {
1978                 return new ZenRule[size];
1979             }
1980         };
1981     }
1982 
1983     public static class Diff {
1984         private final ArrayList<String> lines = new ArrayList<>();
1985 
1986         @Override
toString()1987         public String toString() {
1988             final StringBuilder sb = new StringBuilder("Diff[");
1989             final int N = lines.size();
1990             for (int i = 0; i < N; i++) {
1991                 if (i > 0) {
1992                     sb.append(",\n");
1993                 }
1994                 sb.append(lines.get(i));
1995             }
1996             return sb.append(']').toString();
1997         }
1998 
addLine(String item, String action)1999         private Diff addLine(String item, String action) {
2000             lines.add(item + ":" + action);
2001             return this;
2002         }
2003 
addLine(String item, String subitem, Object from, Object to)2004         public Diff addLine(String item, String subitem, Object from, Object to) {
2005             return addLine(item + "." + subitem, from, to);
2006         }
2007 
addLine(String item, Object from, Object to)2008         public Diff addLine(String item, Object from, Object to) {
2009             return addLine(item, from + "->" + to);
2010         }
2011     }
2012 
2013     /**
2014      * Determines whether dnd behavior should mute all ringer-controlled sounds
2015      * This includes notification, ringer and system sounds
2016      */
areAllPriorityOnlyRingerSoundsMuted(NotificationManager.Policy policy)2017     public static boolean areAllPriorityOnlyRingerSoundsMuted(NotificationManager.Policy
2018             policy) {
2019         boolean allowReminders = (policy.priorityCategories
2020                 & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
2021         boolean allowCalls = (policy.priorityCategories
2022                 & NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) != 0;
2023         boolean allowMessages = (policy.priorityCategories
2024                 & NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
2025         boolean allowEvents = (policy.priorityCategories
2026                 & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0;
2027         boolean allowRepeatCallers = (policy.priorityCategories
2028                 & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
2029         boolean allowConversations = (policy.priorityConversationSenders
2030                 & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0;
2031         boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
2032         boolean allowSystem =  (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
2033         return !allowReminders && !allowCalls && !allowMessages && !allowEvents
2034                 && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem
2035                 && !allowConversations;
2036     }
2037 
2038     /**
2039      * Determines whether dnd behavior should mute all sounds
2040      */
areAllZenBehaviorSoundsMuted(NotificationManager.Policy policy)2041     public static boolean areAllZenBehaviorSoundsMuted(NotificationManager.Policy
2042             policy) {
2043         boolean allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
2044         boolean allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
2045         return !allowAlarms && !allowMedia && areAllPriorityOnlyRingerSoundsMuted(policy);
2046     }
2047 
2048     /**
2049      * Determines if DND is currently overriding the ringer
2050      */
isZenOverridingRinger(int zen, Policy consolidatedPolicy)2051     public static boolean isZenOverridingRinger(int zen, Policy consolidatedPolicy) {
2052         return zen == Global.ZEN_MODE_NO_INTERRUPTIONS
2053                 || zen == Global.ZEN_MODE_ALARMS
2054                 || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
2055                 && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(consolidatedPolicy));
2056     }
2057 
2058     /**
2059      * Determines whether dnd behavior should mute all ringer-controlled sounds
2060      * This includes notification, ringer and system sounds
2061      */
areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config)2062     public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) {
2063         return !config.allowReminders && !config.allowCalls && !config.allowMessages
2064                 && !config.allowEvents && !config.allowRepeatCallers
2065                 && !config.areChannelsBypassingDnd && !config.allowSystem;
2066     }
2067 
2068     /**
2069      * Determines whether dnd mutes all sounds
2070      */
areAllZenBehaviorSoundsMuted(ZenModeConfig config)2071     public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) {
2072         return !config.allowAlarms  && !config.allowMedia
2073                 && areAllPriorityOnlyRingerSoundsMuted(config);
2074     }
2075 
2076     /**
2077      * Returns a description of the current do not disturb settings from config.
2078      * - If turned on manually and end time is known, returns end time.
2079      * - If turned on manually and end time is on forever until turned off, return null if
2080      * describeForeverCondition is false, else return String describing indefinite behavior
2081      * - If turned on by an automatic rule, returns the automatic rule name.
2082      * - If on due to an app, returns the app name.
2083      * - If there's a combination of rules/apps that trigger, then shows the one that will
2084      *  last the longest if applicable.
2085      * @return null if DND is off or describeForeverCondition is false and
2086      * DND is on forever (until turned off)
2087      */
getDescription(Context context, boolean zenOn, ZenModeConfig config, boolean describeForeverCondition)2088     public static String getDescription(Context context, boolean zenOn, ZenModeConfig config,
2089             boolean describeForeverCondition) {
2090         if (!zenOn || config == null) {
2091             return null;
2092         }
2093 
2094         String secondaryText = "";
2095         long latestEndTime = -1;
2096 
2097         // DND turned on by manual rule
2098         if (config.manualRule != null) {
2099             final Uri id = config.manualRule.conditionId;
2100             if (config.manualRule.enabler != null) {
2101                 // app triggered manual rule
2102                 String appName = getOwnerCaption(context, config.manualRule.enabler);
2103                 if (!appName.isEmpty()) {
2104                     secondaryText = appName;
2105                 }
2106             } else {
2107                 if (id == null) {
2108                     // Do not disturb manually triggered to remain on forever until turned off
2109                     if (describeForeverCondition) {
2110                         return context.getString(R.string.zen_mode_forever);
2111                     } else {
2112                         return null;
2113                     }
2114                 } else {
2115                     latestEndTime = tryParseCountdownConditionId(id);
2116                     if (latestEndTime > 0) {
2117                         final CharSequence formattedTime = getFormattedTime(context,
2118                                 latestEndTime, isToday(latestEndTime),
2119                                 context.getUserId());
2120                         secondaryText = context.getString(R.string.zen_mode_until, formattedTime);
2121                     }
2122                 }
2123             }
2124         }
2125 
2126         // DND turned on by an automatic rule
2127         for (ZenRule automaticRule : config.automaticRules.values()) {
2128             if (automaticRule.isAutomaticActive()) {
2129                 if (isValidEventConditionId(automaticRule.conditionId)
2130                         || isValidScheduleConditionId(automaticRule.conditionId)) {
2131                     // set text if automatic rule end time is the latest active rule end time
2132                     long endTime = parseAutomaticRuleEndTime(context, automaticRule.conditionId);
2133                     if (endTime > latestEndTime) {
2134                         latestEndTime = endTime;
2135                         secondaryText = automaticRule.name;
2136                     }
2137                 } else {
2138                     // set text if 3rd party rule
2139                     return automaticRule.name;
2140                 }
2141             }
2142         }
2143 
2144         return !secondaryText.equals("") ? secondaryText : null;
2145     }
2146 
parseAutomaticRuleEndTime(Context context, Uri id)2147     private static long parseAutomaticRuleEndTime(Context context, Uri id) {
2148         if (isValidEventConditionId(id)) {
2149             // cannot look up end times for events
2150             return Long.MAX_VALUE;
2151         }
2152 
2153         if (isValidScheduleConditionId(id)) {
2154             ScheduleCalendar schedule = toScheduleCalendar(id);
2155             long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis());
2156 
2157             // check if automatic rule will end on next alarm
2158             if (schedule.exitAtAlarm()) {
2159                 long nextAlarm = getNextAlarm(context);
2160                 schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm);
2161                 if (schedule.shouldExitForAlarm(endTimeMs)) {
2162                     return nextAlarm;
2163                 }
2164             }
2165 
2166             return endTimeMs;
2167         }
2168 
2169         return -1;
2170     }
2171 
getNextAlarm(Context context)2172     private static long getNextAlarm(Context context) {
2173         final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
2174         final AlarmManager.AlarmClockInfo info = alarms.getNextAlarmClock(context.getUserId());
2175         return info != null ? info.getTriggerTime() : 0;
2176     }
2177 }
2178