Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2007 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 com.android.deskclock;
     18 
     19 import android.app.AlarmManager;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.content.ContentResolver;
     23 import android.content.ContentValues;
     24 import android.content.ContentUris;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.SharedPreferences;
     28 import android.database.Cursor;
     29 import android.net.Uri;
     30 import android.os.Parcel;
     31 import android.provider.Settings;
     32 import android.text.format.DateFormat;
     33 
     34 import java.util.Calendar;
     35 import java.util.HashSet;
     36 import java.util.Set;
     37 
     38 /**
     39  * The Alarms provider supplies info about Alarm Clock settings
     40  */
     41 public class Alarms {
     42 
     43     // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It
     44     // is a public action used in the manifest for receiving Alarm broadcasts
     45     // from the alarm manager.
     46     public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
     47 
     48     // A public action sent by AlarmKlaxon when the alarm has stopped sounding
     49     // for any reason (e.g. because it has been dismissed from AlarmAlertFullScreen,
     50     // or killed due to an incoming phone call, etc).
     51     public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
     52 
     53     // AlarmAlertFullScreen listens for this broadcast intent, so that other applications
     54     // can snooze the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
     55     public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE";
     56 
     57     // AlarmAlertFullScreen listens for this broadcast intent, so that other applications
     58     // can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
     59     public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS";
     60 
     61     // This is a private action used by the AlarmKlaxon to update the UI to
     62     // show the alarm has been killed.
     63     public static final String ALARM_KILLED = "alarm_killed";
     64 
     65     // Extra in the ALARM_KILLED intent to indicate to the user how long the
     66     // alarm played before being killed.
     67     public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout";
     68 
     69     // This string is used to indicate a silent alarm in the db.
     70     public static final String ALARM_ALERT_SILENT = "silent";
     71 
     72     // This intent is sent from the notification when the user cancels the
     73     // snooze alert.
     74     public static final String CANCEL_SNOOZE = "cancel_snooze";
     75 
     76     // This string is used when passing an Alarm object through an intent.
     77     public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm";
     78 
     79     // This extra is the raw Alarm object data. It is used in the
     80     // AlarmManagerService to avoid a ClassNotFoundException when filling in
     81     // the Intent extras.
     82     public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw";
     83 
     84     private static final String PREF_SNOOZE_IDS = "snooze_ids";
     85     private static final String PREF_SNOOZE_TIME = "snooze_time";
     86 
     87     private final static String DM12 = "E h:mm aa";
     88     private final static String DM24 = "E kk:mm";
     89 
     90     private final static String M12 = "h:mm aa";
     91     // Shared with DigitalClock
     92     final static String M24 = "kk:mm";
     93 
     94     final static int INVALID_ALARM_ID = -1;
     95 
     96     /**
     97      * Creates a new Alarm and fills in the given alarm's id.
     98      */
     99     public static long addAlarm(Context context, Alarm alarm) {
    100         ContentValues values = createContentValues(alarm);
    101         Uri uri = context.getContentResolver().insert(
    102                 Alarm.Columns.CONTENT_URI, values);
    103         alarm.id = (int) ContentUris.parseId(uri);
    104 
    105         long timeInMillis = calculateAlarm(alarm);
    106         if (alarm.enabled) {
    107             clearSnoozeIfNeeded(context, timeInMillis);
    108         }
    109         setNextAlert(context);
    110         return timeInMillis;
    111     }
    112 
    113     /**
    114      * Removes an existing Alarm.  If this alarm is snoozing, disables
    115      * snooze.  Sets next alert.
    116      */
    117     public static void deleteAlarm(Context context, int alarmId) {
    118         if (alarmId == INVALID_ALARM_ID) return;
    119 
    120         ContentResolver contentResolver = context.getContentResolver();
    121         /* If alarm is snoozing, lose it */
    122         disableSnoozeAlert(context, alarmId);
    123 
    124         Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId);
    125         contentResolver.delete(uri, "", null);
    126 
    127         setNextAlert(context);
    128     }
    129 
    130     /**
    131      * Queries all alarms
    132      * @return cursor over all alarms
    133      */
    134     public static Cursor getAlarmsCursor(ContentResolver contentResolver) {
    135         return contentResolver.query(
    136                 Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS,
    137                 null, null, Alarm.Columns.DEFAULT_SORT_ORDER);
    138     }
    139 
    140     // Private method to get a more limited set of alarms from the database.
    141     private static Cursor getFilteredAlarmsCursor(
    142             ContentResolver contentResolver) {
    143         return contentResolver.query(Alarm.Columns.CONTENT_URI,
    144                 Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED,
    145                 null, null);
    146     }
    147 
    148     private static ContentValues createContentValues(Alarm alarm) {
    149         ContentValues values = new ContentValues(8);
    150         // Set the alarm_time value if this alarm does not repeat. This will be
    151         // used later to disable expire alarms.
    152         long time = 0;
    153         if (!alarm.daysOfWeek.isRepeatSet()) {
    154             time = calculateAlarm(alarm);
    155         }
    156 
    157         values.put(Alarm.Columns.ENABLED, alarm.enabled ? 1 : 0);
    158         values.put(Alarm.Columns.HOUR, alarm.hour);
    159         values.put(Alarm.Columns.MINUTES, alarm.minutes);
    160         values.put(Alarm.Columns.ALARM_TIME, time);
    161         values.put(Alarm.Columns.DAYS_OF_WEEK, alarm.daysOfWeek.getCoded());
    162         values.put(Alarm.Columns.VIBRATE, alarm.vibrate);
    163         values.put(Alarm.Columns.MESSAGE, alarm.label);
    164 
    165         // A null alert Uri indicates a silent alarm.
    166         values.put(Alarm.Columns.ALERT, alarm.alert == null ? ALARM_ALERT_SILENT
    167                 : alarm.alert.toString());
    168 
    169         return values;
    170     }
    171 
    172     private static void clearSnoozeIfNeeded(Context context, long alarmTime) {
    173         // If this alarm fires before the next snooze, clear the snooze to
    174         // enable this alarm.
    175         SharedPreferences prefs = context.getSharedPreferences(AlarmClock.PREFERENCES, 0);
    176 
    177         // Get the list of snoozed alarms
    178         final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>());
    179         for (String snoozedAlarm : snoozedIds) {
    180             final long snoozeTime = prefs.getLong(getAlarmPrefSnoozeTimeKey(snoozedAlarm), 0);
    181             if (alarmTime < snoozeTime) {
    182                 final int alarmId = Integer.parseInt(snoozedAlarm);
    183                 clearSnoozePreference(context, prefs, alarmId);
    184             }
    185         }
    186     }
    187 
    188     /**
    189      * Return an Alarm object representing the alarm id in the database.
    190      * Returns null if no alarm exists.
    191      */
    192     public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) {
    193         Cursor cursor = contentResolver.query(
    194                 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId),
    195                 Alarm.Columns.ALARM_QUERY_COLUMNS,
    196                 null, null, null);
    197         Alarm alarm = null;
    198         if (cursor != null) {
    199             if (cursor.moveToFirst()) {
    200                 alarm = new Alarm(cursor);
    201             }
    202             cursor.close();
    203         }
    204         return alarm;
    205     }
    206 
    207 
    208     /**
    209      * A convenience method to set an alarm in the Alarms
    210      * content provider.
    211      * @return Time when the alarm will fire.
    212      */
    213     public static long setAlarm(Context context, Alarm alarm) {
    214         ContentValues values = createContentValues(alarm);
    215         ContentResolver resolver = context.getContentResolver();
    216         resolver.update(
    217                 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarm.id),
    218                 values, null, null);
    219 
    220         long timeInMillis = calculateAlarm(alarm);
    221 
    222         if (alarm.enabled) {
    223             // Disable the snooze if we just changed the snoozed alarm. This
    224             // only does work if the snoozed alarm is the same as the given
    225             // alarm.
    226             // TODO: disableSnoozeAlert should have a better name.
    227             disableSnoozeAlert(context, alarm.id);
    228 
    229             // Disable the snooze if this alarm fires before the snoozed alarm.
    230             // This works on every alarm since the user most likely intends to
    231             // have the modified alarm fire next.
    232             clearSnoozeIfNeeded(context, timeInMillis);
    233         }
    234 
    235         setNextAlert(context);
    236 
    237         return timeInMillis;
    238     }
    239 
    240     /**
    241      * A convenience method to enable or disable an alarm.
    242      *
    243      * @param id             corresponds to the _id column
    244      * @param enabled        corresponds to the ENABLED column
    245      */
    246 
    247     public static void enableAlarm(
    248             final Context context, final int id, boolean enabled) {
    249         enableAlarmInternal(context, id, enabled);
    250         setNextAlert(context);
    251     }
    252 
    253     private static void enableAlarmInternal(final Context context,
    254             final int id, boolean enabled) {
    255         enableAlarmInternal(context, getAlarm(context.getContentResolver(), id),
    256                 enabled);
    257     }
    258 
    259     private static void enableAlarmInternal(final Context context,
    260             final Alarm alarm, boolean enabled) {
    261         if (alarm == null) {
    262             return;
    263         }
    264         ContentResolver resolver = context.getContentResolver();
    265 
    266         ContentValues values = new ContentValues(2);
    267         values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
    268 
    269         // If we are enabling the alarm, calculate alarm time since the time
    270         // value in Alarm may be old.
    271         if (enabled) {
    272             long time = 0;
    273             if (!alarm.daysOfWeek.isRepeatSet()) {
    274                 time = calculateAlarm(alarm);
    275             }
    276             values.put(Alarm.Columns.ALARM_TIME, time);
    277         } else {
    278             // Clear the snooze if the id matches.
    279             disableSnoozeAlert(context, alarm.id);
    280         }
    281 
    282         resolver.update(ContentUris.withAppendedId(
    283                 Alarm.Columns.CONTENT_URI, alarm.id), values, null, null);
    284     }
    285 
    286     private static Alarm calculateNextAlert(final Context context) {
    287         long minTime = Long.MAX_VALUE;
    288         long now = System.currentTimeMillis();
    289         final SharedPreferences prefs = context.getSharedPreferences(AlarmClock.PREFERENCES, 0);
    290 
    291         Set<Alarm> alarms = new HashSet<Alarm>();
    292 
    293         // We need to to build the list of alarms from both the snoozed list and the scheduled
    294         // list.  For a non-repeating alarm, when it goes of, it becomes disabled.  A snoozed
    295         // non-repeating alarm is not in the active list in the database.
    296 
    297         // first go through the snoozed alarms
    298         final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>());
    299         for (String snoozedAlarm : snoozedIds) {
    300             final int alarmId = Integer.parseInt(snoozedAlarm);
    301             final Alarm a = getAlarm(context.getContentResolver(), alarmId);
    302             alarms.add(a);
    303         }
    304 
    305         // Now add the scheduled alarms
    306         final Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver());
    307         if (cursor != null) {
    308             try {
    309                 if (cursor.moveToFirst()) {
    310                     do {
    311                         final Alarm a = new Alarm(cursor);
    312                         alarms.add(a);
    313                     } while (cursor.moveToNext());
    314                 }
    315             } finally {
    316                 cursor.close();
    317             }
    318         }
    319 
    320         Alarm alarm = null;
    321 
    322         for (Alarm a : alarms) {
    323             // A time of 0 indicates this is a repeating alarm, so
    324             // calculate the time to get the next alert.
    325             if (a.time == 0) {
    326                 a.time = calculateAlarm(a);
    327             }
    328 
    329             // Update the alarm if it has been snoozed
    330             updateAlarmTimeForSnooze(prefs, a);
    331 
    332             if (a.time < now) {
    333                 Log.v("Disabling expired alarm set for " + Log.formatTime(a.time));
    334                 // Expired alarm, disable it and move along.
    335                 enableAlarmInternal(context, a, false);
    336                 continue;
    337             }
    338             if (a.time < minTime) {
    339                 minTime = a.time;
    340                 alarm = a;
    341             }
    342         }
    343 
    344         return alarm;
    345     }
    346 
    347     /**
    348      * Disables non-repeating alarms that have passed.  Called at
    349      * boot.
    350      */
    351     public static void disableExpiredAlarms(final Context context) {
    352         Cursor cur = getFilteredAlarmsCursor(context.getContentResolver());
    353         long now = System.currentTimeMillis();
    354 
    355         try {
    356             if (cur.moveToFirst()) {
    357                 do {
    358                     Alarm alarm = new Alarm(cur);
    359                     // A time of 0 means this alarm repeats. If the time is
    360                     // non-zero, check if the time is before now.
    361                     if (alarm.time != 0 && alarm.time < now) {
    362                         Log.v("Disabling expired alarm set for " +
    363                               Log.formatTime(alarm.time));
    364                         enableAlarmInternal(context, alarm, false);
    365                     }
    366                 } while (cur.moveToNext());
    367             }
    368         } finally {
    369             cur.close();
    370         }
    371     }
    372 
    373     /**
    374      * Called at system startup, on time/timezone change, and whenever
    375      * the user changes alarm settings.  Activates snooze if set,
    376      * otherwise loads all alarms, activates next alert.
    377      */
    378     public static void setNextAlert(final Context context) {
    379         final Alarm alarm = calculateNextAlert(context);
    380         if (alarm != null) {
    381             enableAlert(context, alarm, alarm.time);
    382         } else {
    383             disableAlert(context);
    384         }
    385     }
    386 
    387     /**
    388      * Sets alert in AlarmManger and StatusBar.  This is what will
    389      * actually launch the alert when the alarm triggers.
    390      *
    391      * @param alarm Alarm.
    392      * @param atTimeInMillis milliseconds since epoch
    393      */
    394     private static void enableAlert(Context context, final Alarm alarm,
    395             final long atTimeInMillis) {
    396         AlarmManager am = (AlarmManager)
    397                 context.getSystemService(Context.ALARM_SERVICE);
    398 
    399         if (Log.LOGV) {
    400             Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis);
    401         }
    402 
    403         Intent intent = new Intent(ALARM_ALERT_ACTION);
    404 
    405         // XXX: This is a slight hack to avoid an exception in the remote
    406         // AlarmManagerService process. The AlarmManager adds extra data to
    407         // this Intent which causes it to inflate. Since the remote process
    408         // does not know about the Alarm class, it throws a
    409         // ClassNotFoundException.
    410         //
    411         // To avoid this, we marshall the data ourselves and then parcel a plain
    412         // byte[] array. The AlarmReceiver class knows to build the Alarm
    413         // object from the byte[] array.
    414         Parcel out = Parcel.obtain();
    415         alarm.writeToParcel(out, 0);
    416         out.setDataPosition(0);
    417         intent.putExtra(ALARM_RAW_DATA, out.marshall());
    418 
    419         PendingIntent sender = PendingIntent.getBroadcast(
    420                 context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    421 
    422         am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
    423 
    424         setStatusBarIcon(context, true);
    425 
    426         Calendar c = Calendar.getInstance();
    427         c.setTimeInMillis(atTimeInMillis);
    428         String timeString = formatDayAndTime(context, c);
    429         saveNextAlarm(context, timeString);
    430     }
    431 
    432     /**
    433      * Disables alert in AlarmManger and StatusBar.
    434      *
    435      * @param id Alarm ID.
    436      */
    437     static void disableAlert(Context context) {
    438         AlarmManager am = (AlarmManager)
    439                 context.getSystemService(Context.ALARM_SERVICE);
    440         PendingIntent sender = PendingIntent.getBroadcast(
    441                 context, 0, new Intent(ALARM_ALERT_ACTION),
    442                 PendingIntent.FLAG_CANCEL_CURRENT);
    443         am.cancel(sender);
    444         setStatusBarIcon(context, false);
    445         saveNextAlarm(context, "");
    446     }
    447 
    448     static void saveSnoozeAlert(final Context context, final int id,
    449             final long time) {
    450         SharedPreferences prefs = context.getSharedPreferences(
    451                 AlarmClock.PREFERENCES, 0);
    452         if (id == INVALID_ALARM_ID) {
    453             clearAllSnoozePreferences(context, prefs);
    454         } else {
    455             final Set<String> snoozedIds =
    456                     prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>());
    457             snoozedIds.add(Integer.toString(id));
    458             final SharedPreferences.Editor ed = prefs.edit();
    459             ed.putStringSet(PREF_SNOOZE_IDS, snoozedIds);
    460             ed.putLong(getAlarmPrefSnoozeTimeKey(id), time);
    461             ed.apply();
    462         }
    463         // Set the next alert after updating the snooze.
    464         setNextAlert(context);
    465     }
    466 
    467     private static String getAlarmPrefSnoozeTimeKey(int id) {
    468         return getAlarmPrefSnoozeTimeKey(Integer.toString(id));
    469     }
    470 
    471     private static String getAlarmPrefSnoozeTimeKey(String id) {
    472         return PREF_SNOOZE_TIME + id;
    473     }
    474 
    475     /**
    476      * Disable the snooze alert if the given id matches the snooze id.
    477      */
    478     static void disableSnoozeAlert(final Context context, final int id) {
    479         SharedPreferences prefs = context.getSharedPreferences(
    480                 AlarmClock.PREFERENCES, 0);
    481         if (hasAlarmBeenSnoozed(prefs, id)) {
    482             // This is the same id so clear the shared prefs.
    483             clearSnoozePreference(context, prefs, id);
    484         }
    485     }
    486 
    487     // Helper to remove the snooze preference. Do not use clear because that
    488     // will erase the clock preferences. Also clear the snooze notification in
    489     // the window shade.
    490     private static void clearSnoozePreference(final Context context,
    491             final SharedPreferences prefs, final int id) {
    492         final String alarmStr = Integer.toString(id);
    493         final Set<String> snoozedIds =
    494                 prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>());
    495         if (snoozedIds.contains(alarmStr)) {
    496             NotificationManager nm = (NotificationManager)
    497                     context.getSystemService(Context.NOTIFICATION_SERVICE);
    498             nm.cancel(id);
    499         }
    500 
    501         final SharedPreferences.Editor ed = prefs.edit();
    502         snoozedIds.remove(alarmStr);
    503         ed.putStringSet(PREF_SNOOZE_IDS, snoozedIds);
    504         ed.remove(getAlarmPrefSnoozeTimeKey(alarmStr));
    505         ed.apply();
    506     }
    507 
    508     private static void clearAllSnoozePreferences(final Context context,
    509             final SharedPreferences prefs) {
    510         NotificationManager nm = (NotificationManager)
    511                 context.getSystemService(Context.NOTIFICATION_SERVICE);
    512         final Set<String> snoozedIds =
    513                 prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>());
    514         final SharedPreferences.Editor ed = prefs.edit();
    515         for (String snoozeId : snoozedIds) {
    516             nm.cancel(Integer.parseInt(snoozeId));
    517             ed.remove(getAlarmPrefSnoozeTimeKey(snoozeId));
    518         }
    519 
    520         ed.remove(PREF_SNOOZE_IDS);
    521         ed.apply();
    522     }
    523 
    524     private static boolean hasAlarmBeenSnoozed(final SharedPreferences prefs, final int alarmId) {
    525         final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, null);
    526 
    527         // Return true if there a valid snoozed alarmId was saved
    528         return snoozedIds != null && snoozedIds.contains(Integer.toString(alarmId));
    529     }
    530 
    531     /**
    532      * Updates the specified Alarm with the additional snooze time.
    533      * Returns a boolean indicating whether the alarm was updated.
    534      */
    535     private static boolean updateAlarmTimeForSnooze(
    536             final SharedPreferences prefs, final Alarm alarm) {
    537         if (!hasAlarmBeenSnoozed(prefs, alarm.id)) {
    538             // No need to modify the alarm
    539             return false;
    540         }
    541 
    542         final long time = prefs.getLong(getAlarmPrefSnoozeTimeKey(alarm.id), -1);
    543         // The time in the database is either 0 (repeating) or a specific time
    544         // for a non-repeating alarm. Update this value so the AlarmReceiver
    545         // has the right time to compare.
    546         alarm.time = time;
    547 
    548         return true;
    549     }
    550 
    551     /**
    552      * Tells the StatusBar whether the alarm is enabled or disabled
    553      */
    554     private static void setStatusBarIcon(Context context, boolean enabled) {
    555         Intent alarmChanged = new Intent("android.intent.action.ALARM_CHANGED");
    556         alarmChanged.putExtra("alarmSet", enabled);
    557         context.sendBroadcast(alarmChanged);
    558     }
    559 
    560     private static long calculateAlarm(Alarm alarm) {
    561         return calculateAlarm(alarm.hour, alarm.minutes, alarm.daysOfWeek)
    562                 .getTimeInMillis();
    563     }
    564 
    565     /**
    566      * Given an alarm in hours and minutes, return a time suitable for
    567      * setting in AlarmManager.
    568      */
    569     static Calendar calculateAlarm(int hour, int minute,
    570             Alarm.DaysOfWeek daysOfWeek) {
    571 
    572         // start with now
    573         Calendar c = Calendar.getInstance();
    574         c.setTimeInMillis(System.currentTimeMillis());
    575 
    576         int nowHour = c.get(Calendar.HOUR_OF_DAY);
    577         int nowMinute = c.get(Calendar.MINUTE);
    578 
    579         // if alarm is behind current time, advance one day
    580         if (hour < nowHour  ||
    581             hour == nowHour && minute <= nowMinute) {
    582             c.add(Calendar.DAY_OF_YEAR, 1);
    583         }
    584         c.set(Calendar.HOUR_OF_DAY, hour);
    585         c.set(Calendar.MINUTE, minute);
    586         c.set(Calendar.SECOND, 0);
    587         c.set(Calendar.MILLISECOND, 0);
    588 
    589         int addDays = daysOfWeek.getNextAlarm(c);
    590         if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays);
    591         return c;
    592     }
    593 
    594     static String formatTime(final Context context, int hour, int minute,
    595                              Alarm.DaysOfWeek daysOfWeek) {
    596         Calendar c = calculateAlarm(hour, minute, daysOfWeek);
    597         return formatTime(context, c);
    598     }
    599 
    600     /* used by AlarmAlert */
    601     static String formatTime(final Context context, Calendar c) {
    602         String format = get24HourMode(context) ? M24 : M12;
    603         return (c == null) ? "" : (String)DateFormat.format(format, c);
    604     }
    605 
    606     /**
    607      * Shows day and time -- used for lock screen
    608      */
    609     private static String formatDayAndTime(final Context context, Calendar c) {
    610         String format = get24HourMode(context) ? DM24 : DM12;
    611         return (c == null) ? "" : (String)DateFormat.format(format, c);
    612     }
    613 
    614     /**
    615      * Save time of the next alarm, as a formatted string, into the system
    616      * settings so those who care can make use of it.
    617      */
    618     static void saveNextAlarm(final Context context, String timeString) {
    619         Settings.System.putString(context.getContentResolver(),
    620                                   Settings.System.NEXT_ALARM_FORMATTED,
    621                                   timeString);
    622     }
    623 
    624     /**
    625      * @return true if clock is set to 24-hour mode
    626      */
    627     static boolean get24HourMode(final Context context) {
    628         return android.text.format.DateFormat.is24HourFormat(context);
    629     }
    630 }
    631