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