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