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