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