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