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