blob: 68a3d30b7e91ebc66337ac991376a75d93f212ca [file] [log] [blame]
John Spurlock056c5192014-04-20 21:52:01 -04001/**
2 * Copyright (c) 2014, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 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
17package android.service.notification;
18
John Spurlock3b98b3f2014-05-01 09:08:48 -040019import android.content.ComponentName;
20import android.net.Uri;
John Spurlock056c5192014-04-20 21:52:01 -040021import android.os.Parcel;
22import android.os.Parcelable;
23import android.text.TextUtils;
John Spurlock856edeb2014-06-01 20:36:47 -040024import android.util.Slog;
John Spurlock056c5192014-04-20 21:52:01 -040025
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28import org.xmlpull.v1.XmlSerializer;
29
30import java.io.IOException;
John Spurlock3b98b3f2014-05-01 09:08:48 -040031import java.util.ArrayList;
32import java.util.Arrays;
John Spurlock056c5192014-04-20 21:52:01 -040033import java.util.Objects;
34
35/**
36 * Persisted configuration for zen mode.
37 *
38 * @hide
39 */
40public class ZenModeConfig implements Parcelable {
John Spurlock856edeb2014-06-01 20:36:47 -040041 private static String TAG = "ZenModeConfig";
John Spurlock056c5192014-04-20 21:52:01 -040042
43 public static final String SLEEP_MODE_NIGHTS = "nights";
44 public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
45
Chris Wren99f963e2014-05-28 16:52:42 -040046 public static final int SOURCE_ANYONE = 0;
47 public static final int SOURCE_CONTACT = 1;
48 public static final int SOURCE_STAR = 2;
49 public static final int MAX_SOURCE = SOURCE_STAR;
50
John Spurlock056c5192014-04-20 21:52:01 -040051 private static final int XML_VERSION = 1;
52 private static final String ZEN_TAG = "zen";
53 private static final String ZEN_ATT_VERSION = "version";
54 private static final String ALLOW_TAG = "allow";
55 private static final String ALLOW_ATT_CALLS = "calls";
56 private static final String ALLOW_ATT_MESSAGES = "messages";
Chris Wren99f963e2014-05-28 16:52:42 -040057 private static final String ALLOW_ATT_FROM = "from";
John Spurlock056c5192014-04-20 21:52:01 -040058 private static final String SLEEP_TAG = "sleep";
59 private static final String SLEEP_ATT_MODE = "mode";
60
61 private static final String SLEEP_ATT_START_HR = "startHour";
62 private static final String SLEEP_ATT_START_MIN = "startMin";
63 private static final String SLEEP_ATT_END_HR = "endHour";
64 private static final String SLEEP_ATT_END_MIN = "endMin";
65
John Spurlock3b98b3f2014-05-01 09:08:48 -040066 private static final String CONDITION_TAG = "condition";
67 private static final String CONDITION_ATT_COMPONENT = "component";
68 private static final String CONDITION_ATT_ID = "id";
69
John Spurlock856edeb2014-06-01 20:36:47 -040070 private static final String EXIT_CONDITION_TAG = "exitCondition";
71 private static final String EXIT_CONDITION_ATT_ID = "id";
72
John Spurlock056c5192014-04-20 21:52:01 -040073 public boolean allowCalls;
74 public boolean allowMessages;
Chris Wren99f963e2014-05-28 16:52:42 -040075 public int allowFrom = SOURCE_ANYONE;
John Spurlock056c5192014-04-20 21:52:01 -040076
77 public String sleepMode;
78 public int sleepStartHour;
79 public int sleepStartMinute;
80 public int sleepEndHour;
81 public int sleepEndMinute;
John Spurlock3b98b3f2014-05-01 09:08:48 -040082 public ComponentName[] conditionComponents;
83 public Uri[] conditionIds;
John Spurlock856edeb2014-06-01 20:36:47 -040084 public Uri exitConditionId;
John Spurlock056c5192014-04-20 21:52:01 -040085
86 public ZenModeConfig() { }
87
88 public ZenModeConfig(Parcel source) {
89 allowCalls = source.readInt() == 1;
90 allowMessages = source.readInt() == 1;
91 if (source.readInt() == 1) {
92 sleepMode = source.readString();
93 }
94 sleepStartHour = source.readInt();
95 sleepStartMinute = source.readInt();
96 sleepEndHour = source.readInt();
97 sleepEndMinute = source.readInt();
John Spurlock3b98b3f2014-05-01 09:08:48 -040098 int len = source.readInt();
99 if (len > 0) {
100 conditionComponents = new ComponentName[len];
101 source.readTypedArray(conditionComponents, ComponentName.CREATOR);
102 }
103 len = source.readInt();
104 if (len > 0) {
105 conditionIds = new Uri[len];
106 source.readTypedArray(conditionIds, Uri.CREATOR);
107 }
Chris Wren99f963e2014-05-28 16:52:42 -0400108 allowFrom = source.readInt();
John Spurlock856edeb2014-06-01 20:36:47 -0400109 exitConditionId = source.readParcelable(null);
John Spurlock056c5192014-04-20 21:52:01 -0400110 }
111
112 @Override
113 public void writeToParcel(Parcel dest, int flags) {
114 dest.writeInt(allowCalls ? 1 : 0);
115 dest.writeInt(allowMessages ? 1 : 0);
116 if (sleepMode != null) {
117 dest.writeInt(1);
118 dest.writeString(sleepMode);
119 } else {
120 dest.writeInt(0);
121 }
122 dest.writeInt(sleepStartHour);
123 dest.writeInt(sleepStartMinute);
124 dest.writeInt(sleepEndHour);
125 dest.writeInt(sleepEndMinute);
John Spurlock3b98b3f2014-05-01 09:08:48 -0400126 if (conditionComponents != null && conditionComponents.length > 0) {
127 dest.writeInt(conditionComponents.length);
128 dest.writeTypedArray(conditionComponents, 0);
129 } else {
130 dest.writeInt(0);
131 }
132 if (conditionIds != null && conditionIds.length > 0) {
133 dest.writeInt(conditionIds.length);
134 dest.writeTypedArray(conditionIds, 0);
135 } else {
136 dest.writeInt(0);
137 }
Chris Wren99f963e2014-05-28 16:52:42 -0400138 dest.writeInt(allowFrom);
John Spurlock856edeb2014-06-01 20:36:47 -0400139 dest.writeParcelable(exitConditionId, 0);
John Spurlock056c5192014-04-20 21:52:01 -0400140 }
141
142 @Override
143 public String toString() {
144 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
145 .append("allowCalls=").append(allowCalls)
146 .append(",allowMessages=").append(allowMessages)
Chris Wren99f963e2014-05-28 16:52:42 -0400147 .append(",allowFrom=").append(sourceToString(allowFrom))
John Spurlock056c5192014-04-20 21:52:01 -0400148 .append(",sleepMode=").append(sleepMode)
149 .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
150 .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
John Spurlock3b98b3f2014-05-01 09:08:48 -0400151 .append(",conditionComponents=")
152 .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
153 .append(",conditionIds=")
154 .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
John Spurlock856edeb2014-06-01 20:36:47 -0400155 .append(",exitConditionId=").append(exitConditionId)
John Spurlock056c5192014-04-20 21:52:01 -0400156 .append(']').toString();
157 }
158
Chris Wren99f963e2014-05-28 16:52:42 -0400159 public static String sourceToString(int source) {
160 switch (source) {
161 case SOURCE_ANYONE:
162 return "anyone";
163 case SOURCE_CONTACT:
164 return "contacts";
165 case SOURCE_STAR:
166 return "stars";
167 default:
168 return "UNKNOWN";
169 }
170 }
171
John Spurlock056c5192014-04-20 21:52:01 -0400172 @Override
173 public boolean equals(Object o) {
174 if (!(o instanceof ZenModeConfig)) return false;
175 if (o == this) return true;
176 final ZenModeConfig other = (ZenModeConfig) o;
177 return other.allowCalls == allowCalls
178 && other.allowMessages == allowMessages
Chris Wren99f963e2014-05-28 16:52:42 -0400179 && other.allowFrom == allowFrom
John Spurlock056c5192014-04-20 21:52:01 -0400180 && Objects.equals(other.sleepMode, sleepMode)
181 && other.sleepStartHour == sleepStartHour
182 && other.sleepStartMinute == sleepStartMinute
183 && other.sleepEndHour == sleepEndHour
John Spurlock3b98b3f2014-05-01 09:08:48 -0400184 && other.sleepEndMinute == sleepEndMinute
185 && Objects.deepEquals(other.conditionComponents, conditionComponents)
John Spurlock856edeb2014-06-01 20:36:47 -0400186 && Objects.deepEquals(other.conditionIds, conditionIds)
187 && Objects.equals(other.exitConditionId, exitConditionId);
John Spurlock056c5192014-04-20 21:52:01 -0400188 }
189
190 @Override
191 public int hashCode() {
Chris Wren99f963e2014-05-28 16:52:42 -0400192 return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode,
193 sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
John Spurlock856edeb2014-06-01 20:36:47 -0400194 Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
195 exitConditionId);
John Spurlock056c5192014-04-20 21:52:01 -0400196 }
197
198 public boolean isValid() {
199 return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
200 && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
201 && (sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
202 || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS));
203 }
204
205 public static ZenModeConfig readXml(XmlPullParser parser)
206 throws XmlPullParserException, IOException {
207 int type = parser.getEventType();
208 if (type != XmlPullParser.START_TAG) return null;
209 String tag = parser.getName();
210 if (!ZEN_TAG.equals(tag)) return null;
211 final ZenModeConfig rt = new ZenModeConfig();
212 final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION));
John Spurlock3b98b3f2014-05-01 09:08:48 -0400213 final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
214 final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
John Spurlock056c5192014-04-20 21:52:01 -0400215 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
216 tag = parser.getName();
John Spurlock3b98b3f2014-05-01 09:08:48 -0400217 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
218 if (!conditionComponents.isEmpty()) {
219 rt.conditionComponents = conditionComponents
220 .toArray(new ComponentName[conditionComponents.size()]);
221 rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
222 }
223 return rt;
224 }
John Spurlock056c5192014-04-20 21:52:01 -0400225 if (type == XmlPullParser.START_TAG) {
226 if (ALLOW_TAG.equals(tag)) {
227 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
228 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
Chris Wren99f963e2014-05-28 16:52:42 -0400229 rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
230 if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
231 throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
232 }
John Spurlock056c5192014-04-20 21:52:01 -0400233 } else if (SLEEP_TAG.equals(tag)) {
234 final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
235 rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode)
236 || SLEEP_MODE_WEEKNIGHTS.equals(mode)) ? mode : null;
237 final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
238 final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
239 final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
240 final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
241 rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
242 rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
243 rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
244 rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
John Spurlock3b98b3f2014-05-01 09:08:48 -0400245 } else if (CONDITION_TAG.equals(tag)) {
246 final ComponentName component =
247 safeComponentName(parser, CONDITION_ATT_COMPONENT);
248 final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
249 if (component != null && conditionId != null) {
250 conditionComponents.add(component);
251 conditionIds.add(conditionId);
252 }
John Spurlock856edeb2014-06-01 20:36:47 -0400253 } else if (EXIT_CONDITION_TAG.equals(tag)) {
254 rt.exitConditionId = safeUri(parser, EXIT_CONDITION_ATT_ID);
John Spurlock056c5192014-04-20 21:52:01 -0400255 }
256 }
257 }
John Spurlock3b98b3f2014-05-01 09:08:48 -0400258 throw new IllegalStateException("Failed to reach END_DOCUMENT");
John Spurlock056c5192014-04-20 21:52:01 -0400259 }
260
261 public void writeXml(XmlSerializer out) throws IOException {
262 out.startTag(null, ZEN_TAG);
263 out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION));
264
265 out.startTag(null, ALLOW_TAG);
266 out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
267 out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
Chris Wren99f963e2014-05-28 16:52:42 -0400268 out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
John Spurlock056c5192014-04-20 21:52:01 -0400269 out.endTag(null, ALLOW_TAG);
270
271 out.startTag(null, SLEEP_TAG);
272 if (sleepMode != null) {
273 out.attribute(null, SLEEP_ATT_MODE, sleepMode);
274 }
275 out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
276 out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
277 out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
278 out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
279 out.endTag(null, SLEEP_TAG);
280
John Spurlock3b98b3f2014-05-01 09:08:48 -0400281 if (conditionComponents != null && conditionIds != null
282 && conditionComponents.length == conditionIds.length) {
283 for (int i = 0; i < conditionComponents.length; i++) {
284 out.startTag(null, CONDITION_TAG);
285 out.attribute(null, CONDITION_ATT_COMPONENT,
286 conditionComponents[i].flattenToString());
287 out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
288 out.endTag(null, CONDITION_TAG);
289 }
290 }
John Spurlock856edeb2014-06-01 20:36:47 -0400291 if (exitConditionId != null) {
292 out.startTag(null, EXIT_CONDITION_TAG);
293 out.attribute(null, EXIT_CONDITION_ATT_ID, exitConditionId.toString());
294 out.endTag(null, EXIT_CONDITION_TAG);
295 }
John Spurlock056c5192014-04-20 21:52:01 -0400296 out.endTag(null, ZEN_TAG);
297 }
298
299 public static boolean isValidHour(int val) {
300 return val >= 0 && val < 24;
301 }
302
303 public static boolean isValidMinute(int val) {
304 return val >= 0 && val < 60;
305 }
306
307 private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) {
308 final String val = parser.getAttributeValue(null, att);
309 if (TextUtils.isEmpty(val)) return defValue;
310 return Boolean.valueOf(val);
311 }
312
313 private static int safeInt(XmlPullParser parser, String att, int defValue) {
314 final String val = parser.getAttributeValue(null, att);
315 if (TextUtils.isEmpty(val)) return defValue;
316 return Integer.valueOf(val);
317 }
318
John Spurlock3b98b3f2014-05-01 09:08:48 -0400319 private static ComponentName safeComponentName(XmlPullParser parser, String att) {
320 final String val = parser.getAttributeValue(null, att);
321 if (TextUtils.isEmpty(val)) return null;
322 return ComponentName.unflattenFromString(val);
323 }
324
325 private static Uri safeUri(XmlPullParser parser, String att) {
326 final String val = parser.getAttributeValue(null, att);
327 if (TextUtils.isEmpty(val)) return null;
328 return Uri.parse(val);
329 }
330
John Spurlock056c5192014-04-20 21:52:01 -0400331 @Override
332 public int describeContents() {
333 return 0;
334 }
335
336 public ZenModeConfig copy() {
337 final Parcel parcel = Parcel.obtain();
338 try {
339 writeToParcel(parcel, 0);
340 parcel.setDataPosition(0);
341 return new ZenModeConfig(parcel);
342 } finally {
343 parcel.recycle();
344 }
345 }
346
347 public static final Parcelable.Creator<ZenModeConfig> CREATOR
348 = new Parcelable.Creator<ZenModeConfig>() {
349 @Override
350 public ZenModeConfig createFromParcel(Parcel source) {
351 return new ZenModeConfig(source);
352 }
353
354 @Override
355 public ZenModeConfig[] newArray(int size) {
356 return new ZenModeConfig[size];
357 }
358 };
John Spurlock856edeb2014-06-01 20:36:47 -0400359
360 // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951
361
362 private static final String COUNTDOWN_AUTHORITY = "android";
363 private static final String COUNTDOWN_PATH = "countdown";
364
365 public static Uri toCountdownConditionId(long time) {
366 return new Uri.Builder().scheme(Condition.SCHEME)
367 .authority(COUNTDOWN_AUTHORITY)
368 .appendPath(COUNTDOWN_PATH)
369 .appendPath(Long.toString(time))
370 .build();
371 }
372
373 public static long tryParseCountdownConditionId(Uri conditionId) {
374 if (!Condition.isValidId(conditionId, COUNTDOWN_AUTHORITY)) return 0;
375 if (conditionId.getPathSegments().size() != 2
376 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0;
377 try {
378 return Long.parseLong(conditionId.getPathSegments().get(1));
379 } catch (RuntimeException e) {
380 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e);
381 return 0;
382 }
383 }
384
385 public static boolean isValidCountdownConditionId(Uri conditionId) {
386 return tryParseCountdownConditionId(conditionId) != 0;
387 }
John Spurlock056c5192014-04-20 21:52:01 -0400388}