• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
addAlarm(Context context, Alarm alarm)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      */
deleteAlarm(Context context, int alarmId)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 
getAlarmsCursorLoader(Context context)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      */
getAlarmsCursor(ContentResolver contentResolver)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.
getFilteredAlarmsCursor( ContentResolver contentResolver)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 
createContentValues(Alarm alarm)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 
clearSnoozeIfNeeded(Context context, long alarmTime)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      */
getAlarm(ContentResolver contentResolver, int alarmId)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      */
setAlarm(Context context, Alarm alarm)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 
enableAlarm( final Context context, final int id, boolean enabled)276     public static void enableAlarm(
277             final Context context, final int id, boolean enabled) {
278         enableAlarmInternal(context, id, enabled);
279         setNextAlert(context);
280     }
281 
enableAlarmInternal(final Context context, final int id, boolean enabled)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 
enableAlarmInternal(final Context context, final Alarm alarm, boolean enabled)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 
calculateNextAlert(final Context context)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      */
disableExpiredAlarms(final Context context)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      */
setNextAlert(final Context context)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      */
enableAlert(Context context, final Alarm alarm, final long atTimeInMillis)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      */
disableAlert(Context context)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 
saveSnoozeAlert(final Context context, final int id, final long time)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 
getAlarmPrefSnoozeTimeKey(int id)500     private static String getAlarmPrefSnoozeTimeKey(int id) {
501         return getAlarmPrefSnoozeTimeKey(Integer.toString(id));
502     }
503 
getAlarmPrefSnoozeTimeKey(String id)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      */
disableSnoozeAlert(final Context context, final int id)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.
clearSnoozePreference(final Context context, final SharedPreferences prefs, final int id)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 
clearAllSnoozePreferences(final Context context, final SharedPreferences prefs)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 
hasAlarmBeenSnoozed(final SharedPreferences prefs, final int alarmId)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      */
updateAlarmTimeForSnooze( final SharedPreferences prefs, final Alarm alarm)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      */
setStatusBarIcon(Context context, boolean enabled)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 
calculateAlarm(Alarm alarm)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      */
calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek)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 
formatTime(final Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek)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 */
formatTime(final Context context, Calendar c)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      */
formatDayAndTime(final Context context, Calendar c)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      */
saveNextAlarm(final Context context, String timeString)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      */
get24HourMode(final Context context)661     public static boolean get24HourMode(final Context context) {
662         return android.text.format.DateFormat.is24HourFormat(context);
663     }
664 }
665