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