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