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