ZenModeConfig.java revision fc746f8ac5ea74747a502d4a75161a46f9cb892d
1/** 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 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.res.Resources; 22import android.net.Uri; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.text.TextUtils; 26import android.text.format.DateFormat; 27import android.util.Slog; 28 29import org.xmlpull.v1.XmlPullParser; 30import org.xmlpull.v1.XmlPullParserException; 31import org.xmlpull.v1.XmlSerializer; 32 33import java.io.IOException; 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.Calendar; 37import java.util.Locale; 38import java.util.Objects; 39 40import com.android.internal.R; 41 42/** 43 * Persisted configuration for zen mode. 44 * 45 * @hide 46 */ 47public class ZenModeConfig implements Parcelable { 48 private static String TAG = "ZenModeConfig"; 49 50 public static final String SLEEP_MODE_NIGHTS = "nights"; 51 public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; 52 public static final String SLEEP_MODE_DAYS_PREFIX = "days:"; 53 54 public static final int SOURCE_ANYONE = 0; 55 public static final int SOURCE_CONTACT = 1; 56 public static final int SOURCE_STAR = 2; 57 public static final int MAX_SOURCE = SOURCE_STAR; 58 59 public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 60 Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; 61 public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, 62 Calendar.WEDNESDAY, Calendar.THURSDAY }; 63 64 public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; 65 private static final int SECONDS_MS = 1000; 66 private static final int MINUTES_MS = 60 * SECONDS_MS; 67 private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; 68 69 private static final boolean DEFAULT_ALLOW_REMINDERS = true; 70 private static final boolean DEFAULT_ALLOW_EVENTS = true; 71 72 private static final int XML_VERSION = 1; 73 private static final String ZEN_TAG = "zen"; 74 private static final String ZEN_ATT_VERSION = "version"; 75 private static final String ALLOW_TAG = "allow"; 76 private static final String ALLOW_ATT_CALLS = "calls"; 77 private static final String ALLOW_ATT_MESSAGES = "messages"; 78 private static final String ALLOW_ATT_FROM = "from"; 79 private static final String ALLOW_ATT_REMINDERS = "reminders"; 80 private static final String ALLOW_ATT_EVENTS = "events"; 81 private static final String SLEEP_TAG = "sleep"; 82 private static final String SLEEP_ATT_MODE = "mode"; 83 private static final String SLEEP_ATT_NONE = "none"; 84 85 private static final String SLEEP_ATT_START_HR = "startHour"; 86 private static final String SLEEP_ATT_START_MIN = "startMin"; 87 private static final String SLEEP_ATT_END_HR = "endHour"; 88 private static final String SLEEP_ATT_END_MIN = "endMin"; 89 90 private static final String CONDITION_TAG = "condition"; 91 private static final String CONDITION_ATT_COMPONENT = "component"; 92 private static final String CONDITION_ATT_ID = "id"; 93 private static final String CONDITION_ATT_SUMMARY = "summary"; 94 private static final String CONDITION_ATT_LINE1 = "line1"; 95 private static final String CONDITION_ATT_LINE2 = "line2"; 96 private static final String CONDITION_ATT_ICON = "icon"; 97 private static final String CONDITION_ATT_STATE = "state"; 98 private static final String CONDITION_ATT_FLAGS = "flags"; 99 100 private static final String EXIT_CONDITION_TAG = "exitCondition"; 101 private static final String EXIT_CONDITION_ATT_COMPONENT = "component"; 102 103 public boolean allowCalls; 104 public boolean allowMessages; 105 public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; 106 public boolean allowEvents = DEFAULT_ALLOW_EVENTS; 107 public int allowFrom = SOURCE_ANYONE; 108 109 public String sleepMode; 110 public int sleepStartHour; // 0-23 111 public int sleepStartMinute; // 0-59 112 public int sleepEndHour; 113 public int sleepEndMinute; 114 public boolean sleepNone; // false = priority, true = none 115 public ComponentName[] conditionComponents; 116 public Uri[] conditionIds; 117 public Condition exitCondition; 118 public ComponentName exitConditionComponent; 119 120 public ZenModeConfig() { } 121 122 public ZenModeConfig(Parcel source) { 123 allowCalls = source.readInt() == 1; 124 allowMessages = source.readInt() == 1; 125 allowReminders = source.readInt() == 1; 126 allowEvents = source.readInt() == 1; 127 if (source.readInt() == 1) { 128 sleepMode = source.readString(); 129 } 130 sleepStartHour = source.readInt(); 131 sleepStartMinute = source.readInt(); 132 sleepEndHour = source.readInt(); 133 sleepEndMinute = source.readInt(); 134 sleepNone = source.readInt() == 1; 135 int len = source.readInt(); 136 if (len > 0) { 137 conditionComponents = new ComponentName[len]; 138 source.readTypedArray(conditionComponents, ComponentName.CREATOR); 139 } 140 len = source.readInt(); 141 if (len > 0) { 142 conditionIds = new Uri[len]; 143 source.readTypedArray(conditionIds, Uri.CREATOR); 144 } 145 allowFrom = source.readInt(); 146 exitCondition = source.readParcelable(null); 147 exitConditionComponent = source.readParcelable(null); 148 } 149 150 @Override 151 public void writeToParcel(Parcel dest, int flags) { 152 dest.writeInt(allowCalls ? 1 : 0); 153 dest.writeInt(allowMessages ? 1 : 0); 154 dest.writeInt(allowReminders ? 1 : 0); 155 dest.writeInt(allowEvents ? 1 : 0); 156 if (sleepMode != null) { 157 dest.writeInt(1); 158 dest.writeString(sleepMode); 159 } else { 160 dest.writeInt(0); 161 } 162 dest.writeInt(sleepStartHour); 163 dest.writeInt(sleepStartMinute); 164 dest.writeInt(sleepEndHour); 165 dest.writeInt(sleepEndMinute); 166 dest.writeInt(sleepNone ? 1 : 0); 167 if (conditionComponents != null && conditionComponents.length > 0) { 168 dest.writeInt(conditionComponents.length); 169 dest.writeTypedArray(conditionComponents, 0); 170 } else { 171 dest.writeInt(0); 172 } 173 if (conditionIds != null && conditionIds.length > 0) { 174 dest.writeInt(conditionIds.length); 175 dest.writeTypedArray(conditionIds, 0); 176 } else { 177 dest.writeInt(0); 178 } 179 dest.writeInt(allowFrom); 180 dest.writeParcelable(exitCondition, 0); 181 dest.writeParcelable(exitConditionComponent, 0); 182 } 183 184 @Override 185 public String toString() { 186 return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') 187 .append("allowCalls=").append(allowCalls) 188 .append(",allowMessages=").append(allowMessages) 189 .append(",allowFrom=").append(sourceToString(allowFrom)) 190 .append(",allowReminders=").append(allowReminders) 191 .append(",allowEvents=").append(allowEvents) 192 .append(",sleepMode=").append(sleepMode) 193 .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) 194 .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute) 195 .append(",sleepNone=").append(sleepNone) 196 .append(",conditionComponents=") 197 .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents)) 198 .append(",conditionIds=") 199 .append(conditionIds == null ? null : TextUtils.join(",", conditionIds)) 200 .append(",exitCondition=").append(exitCondition) 201 .append(",exitConditionComponent=").append(exitConditionComponent) 202 .append(']').toString(); 203 } 204 205 public static String sourceToString(int source) { 206 switch (source) { 207 case SOURCE_ANYONE: 208 return "anyone"; 209 case SOURCE_CONTACT: 210 return "contacts"; 211 case SOURCE_STAR: 212 return "stars"; 213 default: 214 return "UNKNOWN"; 215 } 216 } 217 218 @Override 219 public boolean equals(Object o) { 220 if (!(o instanceof ZenModeConfig)) return false; 221 if (o == this) return true; 222 final ZenModeConfig other = (ZenModeConfig) o; 223 return other.allowCalls == allowCalls 224 && other.allowMessages == allowMessages 225 && other.allowFrom == allowFrom 226 && other.allowReminders == allowReminders 227 && other.allowEvents == allowEvents 228 && Objects.equals(other.sleepMode, sleepMode) 229 && other.sleepNone == sleepNone 230 && other.sleepStartHour == sleepStartHour 231 && other.sleepStartMinute == sleepStartMinute 232 && other.sleepEndHour == sleepEndHour 233 && other.sleepEndMinute == sleepEndMinute 234 && Objects.deepEquals(other.conditionComponents, conditionComponents) 235 && Objects.deepEquals(other.conditionIds, conditionIds) 236 && Objects.equals(other.exitCondition, exitCondition) 237 && Objects.equals(other.exitConditionComponent, exitConditionComponent); 238 } 239 240 @Override 241 public int hashCode() { 242 return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents, 243 sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour, 244 sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds), 245 exitCondition, exitConditionComponent); 246 } 247 248 public boolean isValid() { 249 return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute) 250 && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute) 251 && isValidSleepMode(sleepMode); 252 } 253 254 public static boolean isValidSleepMode(String sleepMode) { 255 return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS) 256 || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null; 257 } 258 259 public static int[] tryParseDays(String sleepMode) { 260 if (sleepMode == null) return null; 261 sleepMode = sleepMode.trim(); 262 if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS; 263 if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS; 264 if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null; 265 if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null; 266 final String[] tokens = sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()).split(","); 267 if (tokens.length == 0) return null; 268 final int[] rt = new int[tokens.length]; 269 for (int i = 0; i < tokens.length; i++) { 270 final int day = tryParseInt(tokens[i], -1); 271 if (day == -1) return null; 272 rt[i] = day; 273 } 274 return rt; 275 } 276 277 private static int tryParseInt(String value, int defValue) { 278 if (TextUtils.isEmpty(value)) return defValue; 279 try { 280 return Integer.valueOf(value); 281 } catch (NumberFormatException e) { 282 return defValue; 283 } 284 } 285 286 public static ZenModeConfig readXml(XmlPullParser parser) 287 throws XmlPullParserException, IOException { 288 int type = parser.getEventType(); 289 if (type != XmlPullParser.START_TAG) return null; 290 String tag = parser.getName(); 291 if (!ZEN_TAG.equals(tag)) return null; 292 final ZenModeConfig rt = new ZenModeConfig(); 293 final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION); 294 final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>(); 295 final ArrayList<Uri> conditionIds = new ArrayList<Uri>(); 296 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 297 tag = parser.getName(); 298 if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) { 299 if (!conditionComponents.isEmpty()) { 300 rt.conditionComponents = conditionComponents 301 .toArray(new ComponentName[conditionComponents.size()]); 302 rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]); 303 } 304 return rt; 305 } 306 if (type == XmlPullParser.START_TAG) { 307 if (ALLOW_TAG.equals(tag)) { 308 rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); 309 rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); 310 rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, 311 DEFAULT_ALLOW_REMINDERS); 312 rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); 313 rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); 314 if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { 315 throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom); 316 } 317 } else if (SLEEP_TAG.equals(tag)) { 318 final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); 319 rt.sleepMode = isValidSleepMode(mode)? mode : null; 320 rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false); 321 final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0); 322 final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0); 323 final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0); 324 final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0); 325 rt.sleepStartHour = isValidHour(startHour) ? startHour : 0; 326 rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0; 327 rt.sleepEndHour = isValidHour(endHour) ? endHour : 0; 328 rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0; 329 } else if (CONDITION_TAG.equals(tag)) { 330 final ComponentName component = 331 safeComponentName(parser, CONDITION_ATT_COMPONENT); 332 final Uri conditionId = safeUri(parser, CONDITION_ATT_ID); 333 if (component != null && conditionId != null) { 334 conditionComponents.add(component); 335 conditionIds.add(conditionId); 336 } 337 } else if (EXIT_CONDITION_TAG.equals(tag)) { 338 rt.exitCondition = readConditionXml(parser); 339 if (rt.exitCondition != null) { 340 rt.exitConditionComponent = 341 safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); 342 } 343 } 344 } 345 } 346 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 347 } 348 349 public void writeXml(XmlSerializer out) throws IOException { 350 out.startTag(null, ZEN_TAG); 351 out.attribute(null, ZEN_ATT_VERSION, Integer.toString(XML_VERSION)); 352 353 out.startTag(null, ALLOW_TAG); 354 out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); 355 out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); 356 out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); 357 out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); 358 out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); 359 out.endTag(null, ALLOW_TAG); 360 361 out.startTag(null, SLEEP_TAG); 362 if (sleepMode != null) { 363 out.attribute(null, SLEEP_ATT_MODE, sleepMode); 364 } 365 out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone)); 366 out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour)); 367 out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute)); 368 out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour)); 369 out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute)); 370 out.endTag(null, SLEEP_TAG); 371 372 if (conditionComponents != null && conditionIds != null 373 && conditionComponents.length == conditionIds.length) { 374 for (int i = 0; i < conditionComponents.length; i++) { 375 out.startTag(null, CONDITION_TAG); 376 out.attribute(null, CONDITION_ATT_COMPONENT, 377 conditionComponents[i].flattenToString()); 378 out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString()); 379 out.endTag(null, CONDITION_TAG); 380 } 381 } 382 if (exitCondition != null && exitConditionComponent != null) { 383 out.startTag(null, EXIT_CONDITION_TAG); 384 out.attribute(null, EXIT_CONDITION_ATT_COMPONENT, 385 exitConditionComponent.flattenToString()); 386 writeConditionXml(exitCondition, out); 387 out.endTag(null, EXIT_CONDITION_TAG); 388 } 389 out.endTag(null, ZEN_TAG); 390 } 391 392 public static Condition readConditionXml(XmlPullParser parser) { 393 final Uri id = safeUri(parser, CONDITION_ATT_ID); 394 final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); 395 final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); 396 final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); 397 final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); 398 final int state = safeInt(parser, CONDITION_ATT_STATE, -1); 399 final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); 400 try { 401 return new Condition(id, summary, line1, line2, icon, state, flags); 402 } catch (IllegalArgumentException e) { 403 Slog.w(TAG, "Unable to read condition xml", e); 404 return null; 405 } 406 } 407 408 public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException { 409 out.attribute(null, CONDITION_ATT_ID, c.id.toString()); 410 out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); 411 out.attribute(null, CONDITION_ATT_LINE1, c.line1); 412 out.attribute(null, CONDITION_ATT_LINE2, c.line2); 413 out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon)); 414 out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state)); 415 out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags)); 416 } 417 418 public static boolean isValidHour(int val) { 419 return val >= 0 && val < 24; 420 } 421 422 public static boolean isValidMinute(int val) { 423 return val >= 0 && val < 60; 424 } 425 426 private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { 427 final String val = parser.getAttributeValue(null, att); 428 if (TextUtils.isEmpty(val)) return defValue; 429 return Boolean.valueOf(val); 430 } 431 432 private static int safeInt(XmlPullParser parser, String att, int defValue) { 433 final String val = parser.getAttributeValue(null, att); 434 return tryParseInt(val, defValue); 435 } 436 437 private static ComponentName safeComponentName(XmlPullParser parser, String att) { 438 final String val = parser.getAttributeValue(null, att); 439 if (TextUtils.isEmpty(val)) return null; 440 return ComponentName.unflattenFromString(val); 441 } 442 443 private static Uri safeUri(XmlPullParser parser, String att) { 444 final String val = parser.getAttributeValue(null, att); 445 if (TextUtils.isEmpty(val)) return null; 446 return Uri.parse(val); 447 } 448 449 @Override 450 public int describeContents() { 451 return 0; 452 } 453 454 public ZenModeConfig copy() { 455 final Parcel parcel = Parcel.obtain(); 456 try { 457 writeToParcel(parcel, 0); 458 parcel.setDataPosition(0); 459 return new ZenModeConfig(parcel); 460 } finally { 461 parcel.recycle(); 462 } 463 } 464 465 public static final Parcelable.Creator<ZenModeConfig> CREATOR 466 = new Parcelable.Creator<ZenModeConfig>() { 467 @Override 468 public ZenModeConfig createFromParcel(Parcel source) { 469 return new ZenModeConfig(source); 470 } 471 472 @Override 473 public ZenModeConfig[] newArray(int size) { 474 return new ZenModeConfig[size]; 475 } 476 }; 477 478 public DowntimeInfo toDowntimeInfo() { 479 final DowntimeInfo downtime = new DowntimeInfo(); 480 downtime.startHour = sleepStartHour; 481 downtime.startMinute = sleepStartMinute; 482 downtime.endHour = sleepEndHour; 483 downtime.endMinute = sleepEndMinute; 484 downtime.mode = sleepMode; 485 downtime.none = sleepNone; 486 return downtime; 487 } 488 489 public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { 490 final long now = System.currentTimeMillis(); 491 final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; 492 return toTimeCondition(context, now + millis, minutesFromNow, now, userHandle); 493 } 494 495 public static Condition toTimeCondition(Context context, long time, int minutes, long now, 496 int userHandle) { 497 final int num, summaryResId, line1ResId; 498 if (minutes < 60) { 499 // display as minutes 500 num = minutes; 501 summaryResId = R.plurals.zen_mode_duration_minutes_summary; 502 line1ResId = R.plurals.zen_mode_duration_minutes; 503 } else { 504 // display as hours 505 num = Math.round(minutes / 60f); 506 summaryResId = com.android.internal.R.plurals.zen_mode_duration_hours_summary; 507 line1ResId = com.android.internal.R.plurals.zen_mode_duration_hours; 508 } 509 final String skeleton = DateFormat.is24HourFormat(context, userHandle) ? "Hm" : "hma"; 510 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 511 final CharSequence formattedTime = DateFormat.format(pattern, time); 512 final Resources res = context.getResources(); 513 final String summary = res.getQuantityString(summaryResId, num, num, formattedTime); 514 final String line1 = res.getQuantityString(line1ResId, num, num, formattedTime); 515 final String line2 = res.getString(R.string.zen_mode_until, formattedTime); 516 final Uri id = toCountdownConditionId(time); 517 return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE, 518 Condition.FLAG_RELEVANT_NOW); 519 } 520 521 // For built-in conditions 522 public static final String SYSTEM_AUTHORITY = "android"; 523 524 // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 525 public static final String COUNTDOWN_PATH = "countdown"; 526 527 public static Uri toCountdownConditionId(long time) { 528 return new Uri.Builder().scheme(Condition.SCHEME) 529 .authority(SYSTEM_AUTHORITY) 530 .appendPath(COUNTDOWN_PATH) 531 .appendPath(Long.toString(time)) 532 .build(); 533 } 534 535 public static long tryParseCountdownConditionId(Uri conditionId) { 536 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; 537 if (conditionId.getPathSegments().size() != 2 538 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; 539 try { 540 return Long.parseLong(conditionId.getPathSegments().get(1)); 541 } catch (RuntimeException e) { 542 Slog.w(TAG, "Error parsing countdown condition: " + conditionId, e); 543 return 0; 544 } 545 } 546 547 public static boolean isValidCountdownConditionId(Uri conditionId) { 548 return tryParseCountdownConditionId(conditionId) != 0; 549 } 550 551 // Built-in downtime conditions 552 // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false 553 public static final String DOWNTIME_PATH = "downtime"; 554 555 public static Uri toDowntimeConditionId(DowntimeInfo downtime) { 556 return new Uri.Builder().scheme(Condition.SCHEME) 557 .authority(SYSTEM_AUTHORITY) 558 .appendPath(DOWNTIME_PATH) 559 .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute) 560 .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute) 561 .appendQueryParameter("mode", downtime.mode) 562 .appendQueryParameter("none", Boolean.toString(downtime.none)) 563 .build(); 564 } 565 566 public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) { 567 if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY) 568 || conditionId.getPathSegments().size() != 1 569 || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) { 570 return null; 571 } 572 final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); 573 final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); 574 if (start == null || end == null) return null; 575 final DowntimeInfo downtime = new DowntimeInfo(); 576 downtime.startHour = start[0]; 577 downtime.startMinute = start[1]; 578 downtime.endHour = end[0]; 579 downtime.endMinute = end[1]; 580 downtime.mode = conditionId.getQueryParameter("mode"); 581 downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none")); 582 return downtime; 583 } 584 585 private static int[] tryParseHourAndMinute(String value) { 586 if (TextUtils.isEmpty(value)) return null; 587 final int i = value.indexOf('.'); 588 if (i < 1 || i >= value.length() - 1) return null; 589 final int hour = tryParseInt(value.substring(0, i), -1); 590 final int minute = tryParseInt(value.substring(i + 1), -1); 591 return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; 592 } 593 594 public static boolean isValidDowntimeConditionId(Uri conditionId) { 595 return tryParseDowntimeConditionId(conditionId) != null; 596 } 597 598 public static class DowntimeInfo { 599 public int startHour; // 0-23 600 public int startMinute; // 0-59 601 public int endHour; 602 public int endMinute; 603 public String mode; 604 public boolean none; 605 606 @Override 607 public int hashCode() { 608 return 0; 609 } 610 611 @Override 612 public boolean equals(Object o) { 613 if (!(o instanceof DowntimeInfo)) return false; 614 final DowntimeInfo other = (DowntimeInfo) o; 615 return startHour == other.startHour 616 && startMinute == other.startMinute 617 && endHour == other.endHour 618 && endMinute == other.endMinute 619 && Objects.equals(mode, other.mode) 620 && none == other.none; 621 } 622 } 623 624 // built-in next alarm conditions 625 public static final String NEXT_ALARM_PATH = "next_alarm"; 626} 627