• 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.alarmclock;
18 
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.ContentResolver;
22 import android.content.ContentValues;
23 import android.content.ContentUris;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Parcel;
30 import android.provider.Settings;
31 import android.text.format.DateFormat;
32 
33 import java.util.Calendar;
34 import java.text.DateFormatSymbols;
35 
36 /**
37  * The Alarms provider supplies info about Alarm Clock settings
38  */
39 public class Alarms {
40 
41     // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It
42     // is a public action used in the manifest for receiving Alarm broadcasts
43     // from the alarm manager.
44     public static final String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT";
45 
46     // This is a private action used when the user clears all notifications.
47     public static final String CLEAR_NOTIFICATION = "clear_notification";
48 
49     // This is a private action used by the AlarmKlaxon to update the UI to
50     // show the alarm has been killed.
51     public static final String ALARM_KILLED = "alarm_killed";
52 
53     // Extra in the ALARM_KILLED intent to indicate to the user how long the
54     // alarm played before being killed.
55     public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout";
56 
57     // This string is used to indicate a silent alarm in the db.
58     public static final String ALARM_ALERT_SILENT = "silent";
59 
60     // This intent is sent from the notification when the user cancels the
61     // snooze alert.
62     public static final String CANCEL_SNOOZE = "cancel_snooze";
63 
64     // This string is used when passing an Alarm object through an intent.
65     public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm";
66 
67     // This extra is the raw Alarm object data. It is used in the
68     // AlarmManagerService to avoid a ClassNotFoundException when filling in
69     // the Intent extras.
70     public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw";
71 
72     // This string is used to identify the alarm id passed to SetAlarm from the
73     // list of alarms.
74     public static final String ALARM_ID = "alarm_id";
75 
76     final static String PREF_SNOOZE_ID = "snooze_id";
77     final static String PREF_SNOOZE_TIME = "snooze_time";
78 
79     private final static String DM12 = "E h:mm aa";
80     private final static String DM24 = "E k:mm";
81 
82     private final static String M12 = "h:mm aa";
83     // Shared with DigitalClock
84     final static String M24 = "kk:mm";
85 
86     /**
87      * Creates a new Alarm.
88      */
addAlarm(ContentResolver contentResolver)89     public static Uri addAlarm(ContentResolver contentResolver) {
90         ContentValues values = new ContentValues();
91         values.put(Alarm.Columns.HOUR, 8);
92         return contentResolver.insert(Alarm.Columns.CONTENT_URI, values);
93     }
94 
95     /**
96      * Removes an existing Alarm.  If this alarm is snoozing, disables
97      * snooze.  Sets next alert.
98      */
deleteAlarm( Context context, int alarmId)99     public static void deleteAlarm(
100             Context context, int alarmId) {
101 
102         ContentResolver contentResolver = context.getContentResolver();
103         /* If alarm is snoozing, lose it */
104         disableSnoozeAlert(context, alarmId);
105 
106         Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId);
107         contentResolver.delete(uri, "", null);
108 
109         setNextAlert(context);
110     }
111 
112     /**
113      * Queries all alarms
114      * @return cursor over all alarms
115      */
getAlarmsCursor(ContentResolver contentResolver)116     public static Cursor getAlarmsCursor(ContentResolver contentResolver) {
117         return contentResolver.query(
118                 Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS,
119                 null, null, Alarm.Columns.DEFAULT_SORT_ORDER);
120     }
121 
122     // Private method to get a more limited set of alarms from the database.
getFilteredAlarmsCursor( ContentResolver contentResolver)123     private static Cursor getFilteredAlarmsCursor(
124             ContentResolver contentResolver) {
125         return contentResolver.query(Alarm.Columns.CONTENT_URI,
126                 Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED,
127                 null, null);
128     }
129 
130     /**
131      * Return an Alarm object representing the alarm id in the database.
132      * Returns null if no alarm exists.
133      */
getAlarm(ContentResolver contentResolver, int alarmId)134     public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) {
135         Cursor cursor = contentResolver.query(
136                 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId),
137                 Alarm.Columns.ALARM_QUERY_COLUMNS,
138                 null, null, null);
139         Alarm alarm = null;
140         if (cursor != null) {
141             if (cursor.moveToFirst()) {
142                 alarm = new Alarm(cursor);
143             }
144             cursor.close();
145         }
146         return alarm;
147     }
148 
149 
150     /**
151      * A convenience method to set an alarm in the Alarms
152      * content provider.
153      *
154      * @param id             corresponds to the _id column
155      * @param enabled        corresponds to the ENABLED column
156      * @param hour           corresponds to the HOUR column
157      * @param minutes        corresponds to the MINUTES column
158      * @param daysOfWeek     corresponds to the DAYS_OF_WEEK column
159      * @param time           corresponds to the ALARM_TIME column
160      * @param vibrate        corresponds to the VIBRATE column
161      * @param message        corresponds to the MESSAGE column
162      * @param alert          corresponds to the ALERT column
163      */
setAlarm( Context context, int id, boolean enabled, int hour, int minutes, Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, String alert)164     public static void setAlarm(
165             Context context, int id, boolean enabled, int hour, int minutes,
166             Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message,
167             String alert) {
168 
169         ContentValues values = new ContentValues(8);
170         ContentResolver resolver = context.getContentResolver();
171         // Set the alarm_time value if this alarm does not repeat. This will be
172         // used later to disable expired alarms.
173         long time = 0;
174         if (!daysOfWeek.isRepeatSet()) {
175             time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
176         }
177 
178         if (Log.LOGV) Log.v(
179                 "**  setAlarm * idx " + id + " hour " + hour + " minutes " +
180                 minutes + " enabled " + enabled + " time " + time);
181 
182         values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
183         values.put(Alarm.Columns.HOUR, hour);
184         values.put(Alarm.Columns.MINUTES, minutes);
185         values.put(Alarm.Columns.ALARM_TIME, time);
186         values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded());
187         values.put(Alarm.Columns.VIBRATE, vibrate);
188         values.put(Alarm.Columns.MESSAGE, message);
189         values.put(Alarm.Columns.ALERT, alert);
190         resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id),
191                         values, null, null);
192 
193         setNextAlert(context);
194     }
195 
196     /**
197      * A convenience method to enable or disable an alarm.
198      *
199      * @param id             corresponds to the _id column
200      * @param enabled        corresponds to the ENABLED column
201      */
202 
enableAlarm( final Context context, final int id, boolean enabled)203     public static void enableAlarm(
204             final Context context, final int id, boolean enabled) {
205         enableAlarmInternal(context, id, enabled);
206         setNextAlert(context);
207     }
208 
enableAlarmInternal(final Context context, final int id, boolean enabled)209     private static void enableAlarmInternal(final Context context,
210             final int id, boolean enabled) {
211         enableAlarmInternal(context, getAlarm(context.getContentResolver(), id),
212                 enabled);
213     }
214 
enableAlarmInternal(final Context context, final Alarm alarm, boolean enabled)215     private static void enableAlarmInternal(final Context context,
216             final Alarm alarm, boolean enabled) {
217         ContentResolver resolver = context.getContentResolver();
218 
219         ContentValues values = new ContentValues(2);
220         values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
221 
222         // If we are enabling the alarm, calculate alarm time since the time
223         // value in Alarm may be old.
224         if (enabled) {
225             long time = 0;
226             if (!alarm.daysOfWeek.isRepeatSet()) {
227                 time = calculateAlarm(alarm.hour, alarm.minutes,
228                         alarm.daysOfWeek).getTimeInMillis();
229             }
230             values.put(Alarm.Columns.ALARM_TIME, time);
231         }
232 
233         resolver.update(ContentUris.withAppendedId(
234                 Alarm.Columns.CONTENT_URI, alarm.id), values, null, null);
235     }
236 
calculateNextAlert(final Context context)237     public static Alarm calculateNextAlert(final Context context) {
238         Alarm alarm = null;
239         long minTime = Long.MAX_VALUE;
240         long now = System.currentTimeMillis();
241         Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver());
242         if (cursor != null) {
243             if (cursor.moveToFirst()) {
244                 do {
245                     Alarm a = new Alarm(cursor);
246                     // A time of 0 indicates this is a repeating alarm, so
247                     // calculate the time to get the next alert.
248                     if (a.time == 0) {
249                         a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek)
250                                 .getTimeInMillis();
251                     } else if (a.time < now) {
252                         // Expired alarm, disable it and move along.
253                         enableAlarmInternal(context, a, false);
254                         continue;
255                     }
256                     if (a.time < minTime) {
257                         minTime = a.time;
258                         alarm = a;
259                     }
260                 } while (cursor.moveToNext());
261             }
262             cursor.close();
263         }
264         return alarm;
265     }
266 
267     /**
268      * Disables non-repeating alarms that have passed.  Called at
269      * boot.
270      */
disableExpiredAlarms(final Context context)271     public static void disableExpiredAlarms(final Context context) {
272         Cursor cur = getFilteredAlarmsCursor(context.getContentResolver());
273         long now = System.currentTimeMillis();
274 
275         if (cur.moveToFirst()) {
276             do {
277                 Alarm alarm = new Alarm(cur);
278                 // A time of 0 means this alarm repeats. If the time is
279                 // non-zero, check if the time is before now.
280                 if (alarm.time != 0 && alarm.time < now) {
281                     if (Log.LOGV) {
282                         Log.v("** DISABLE " + alarm.id + " now " + now +" set "
283                                 + alarm.time);
284                     }
285                     enableAlarmInternal(context, alarm, false);
286                 }
287             } while (cur.moveToNext());
288         }
289         cur.close();
290     }
291 
292     /**
293      * Called at system startup, on time/timezone change, and whenever
294      * the user changes alarm settings.  Activates snooze if set,
295      * otherwise loads all alarms, activates next alert.
296      */
setNextAlert(final Context context)297     public static void setNextAlert(final Context context) {
298         if (!enableSnoozeAlert(context)) {
299             Alarm alarm = calculateNextAlert(context);
300             if (alarm != null) {
301                 enableAlert(context, alarm, alarm.time);
302             } else {
303                 disableAlert(context);
304             }
305         }
306     }
307 
308     /**
309      * Sets alert in AlarmManger and StatusBar.  This is what will
310      * actually launch the alert when the alarm triggers.
311      *
312      * @param alarm Alarm.
313      * @param atTimeInMillis milliseconds since epoch
314      */
enableAlert(Context context, final Alarm alarm, final long atTimeInMillis)315     private static void enableAlert(Context context, final Alarm alarm,
316             final long atTimeInMillis) {
317         AlarmManager am = (AlarmManager)
318                 context.getSystemService(Context.ALARM_SERVICE);
319 
320         if (Log.LOGV) {
321             Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis);
322         }
323 
324         Intent intent = new Intent(ALARM_ALERT_ACTION);
325 
326         // XXX: This is a slight hack to avoid an exception in the remote
327         // AlarmManagerService process. The AlarmManager adds extra data to
328         // this Intent which causes it to inflate. Since the remote process
329         // does not know about the Alarm class, it throws a
330         // ClassNotFoundException.
331         //
332         // To avoid this, we marshall the data ourselves and then parcel a plain
333         // byte[] array. The AlarmReceiver class knows to build the Alarm
334         // object from the byte[] array.
335         Parcel out = Parcel.obtain();
336         alarm.writeToParcel(out, 0);
337         out.setDataPosition(0);
338         intent.putExtra(ALARM_RAW_DATA, out.marshall());
339 
340         PendingIntent sender = PendingIntent.getBroadcast(
341                 context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
342 
343         am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
344 
345         setStatusBarIcon(context, true);
346 
347         Calendar c = Calendar.getInstance();
348         c.setTime(new java.util.Date(atTimeInMillis));
349         String timeString = formatDayAndTime(context, c);
350         saveNextAlarm(context, timeString);
351     }
352 
353     /**
354      * Disables alert in AlarmManger and StatusBar.
355      *
356      * @param id Alarm ID.
357      */
disableAlert(Context context)358     static void disableAlert(Context context) {
359         AlarmManager am = (AlarmManager)
360                 context.getSystemService(Context.ALARM_SERVICE);
361         PendingIntent sender = PendingIntent.getBroadcast(
362                 context, 0, new Intent(ALARM_ALERT_ACTION),
363                 PendingIntent.FLAG_CANCEL_CURRENT);
364         am.cancel(sender);
365         setStatusBarIcon(context, false);
366         saveNextAlarm(context, "");
367     }
368 
saveSnoozeAlert(final Context context, final int id, final long time)369     static void saveSnoozeAlert(final Context context, final int id,
370             final long time) {
371         SharedPreferences prefs = context.getSharedPreferences(
372                 AlarmClock.PREFERENCES, 0);
373         SharedPreferences.Editor ed = prefs.edit();
374         if (id == -1) {
375             clearSnoozePreference(ed);
376         } else {
377             ed.putInt(PREF_SNOOZE_ID, id);
378             ed.putLong(PREF_SNOOZE_TIME, time);
379             ed.commit();
380         }
381         // Set the next alert after updating the snooze.
382         setNextAlert(context);
383     }
384 
385     /**
386      * Disable the snooze alert if the given id matches the snooze id.
387      */
disableSnoozeAlert(final Context context, final int id)388     static void disableSnoozeAlert(final Context context, final int id) {
389         SharedPreferences prefs = context.getSharedPreferences(
390                 AlarmClock.PREFERENCES, 0);
391         int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1);
392         if (snoozeId == -1) {
393             // No snooze set, do nothing.
394             return;
395         } else if (snoozeId == id) {
396             // This is the same id so clear the shared prefs.
397             clearSnoozePreference(prefs.edit());
398         }
399     }
400 
401     // Helper to remove the snooze preference. Do not use clear because that
402     // will erase the clock preferences.
clearSnoozePreference(final SharedPreferences.Editor ed)403     private static void clearSnoozePreference(final SharedPreferences.Editor ed) {
404         ed.remove(PREF_SNOOZE_ID);
405         ed.remove(PREF_SNOOZE_TIME);
406         ed.commit();
407     };
408 
409     /**
410      * If there is a snooze set, enable it in AlarmManager
411      * @return true if snooze is set
412      */
enableSnoozeAlert(final Context context)413     private static boolean enableSnoozeAlert(final Context context) {
414         SharedPreferences prefs = context.getSharedPreferences(
415                 AlarmClock.PREFERENCES, 0);
416 
417         int id = prefs.getInt(PREF_SNOOZE_ID, -1);
418         if (id == -1) {
419             return false;
420         }
421         long time = prefs.getLong(PREF_SNOOZE_TIME, -1);
422 
423         // Get the alarm from the db.
424         final Alarm alarm = getAlarm(context.getContentResolver(), id);
425         // The time in the database is either 0 (repeating) or a specific time
426         // for a non-repeating alarm. Update this value so the AlarmReceiver
427         // has the right time to compare.
428         alarm.time = time;
429 
430         enableAlert(context, alarm, time);
431         return true;
432     }
433 
434     /**
435      * Tells the StatusBar whether the alarm is enabled or disabled
436      */
setStatusBarIcon(Context context, boolean enabled)437     private static void setStatusBarIcon(Context context, boolean enabled) {
438         Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED);
439         alarmChanged.putExtra("alarmSet", enabled);
440         context.sendBroadcast(alarmChanged);
441     }
442 
443     /**
444      * Given an alarm in hours and minutes, return a time suitable for
445      * setting in AlarmManager.
446      * @param hour Always in 24 hour 0-23
447      * @param minute 0-59
448      * @param daysOfWeek 0-59
449      */
calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek)450     static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) {
451 
452         // start with now
453         Calendar c = Calendar.getInstance();
454         c.setTimeInMillis(System.currentTimeMillis());
455 
456         int nowHour = c.get(Calendar.HOUR_OF_DAY);
457         int nowMinute = c.get(Calendar.MINUTE);
458 
459         // if alarm is behind current time, advance one day
460         if (hour < nowHour  ||
461             hour == nowHour && minute <= nowMinute) {
462             c.add(Calendar.DAY_OF_YEAR, 1);
463         }
464         c.set(Calendar.HOUR_OF_DAY, hour);
465         c.set(Calendar.MINUTE, minute);
466         c.set(Calendar.SECOND, 0);
467         c.set(Calendar.MILLISECOND, 0);
468 
469         int addDays = daysOfWeek.getNextAlarm(c);
470         /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour +
471            " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " +
472            addDays); */
473         if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays);
474         return c;
475     }
476 
formatTime(final Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek)477     static String formatTime(final Context context, int hour, int minute,
478                              Alarm.DaysOfWeek daysOfWeek) {
479         Calendar c = calculateAlarm(hour, minute, daysOfWeek);
480         return formatTime(context, c);
481     }
482 
483     /* used by AlarmAlert */
formatTime(final Context context, Calendar c)484     static String formatTime(final Context context, Calendar c) {
485         String format = get24HourMode(context) ? M24 : M12;
486         return (c == null) ? "" : (String)DateFormat.format(format, c);
487     }
488 
489     /**
490      * Shows day and time -- used for lock screen
491      */
formatDayAndTime(final Context context, Calendar c)492     private static String formatDayAndTime(final Context context, Calendar c) {
493         String format = get24HourMode(context) ? DM24 : DM12;
494         return (c == null) ? "" : (String)DateFormat.format(format, c);
495     }
496 
497     /**
498      * Save time of the next alarm, as a formatted string, into the system
499      * settings so those who care can make use of it.
500      */
saveNextAlarm(final Context context, String timeString)501     static void saveNextAlarm(final Context context, String timeString) {
502         Settings.System.putString(context.getContentResolver(),
503                                   Settings.System.NEXT_ALARM_FORMATTED,
504                                   timeString);
505     }
506 
507     /**
508      * @return true if clock is set to 24-hour mode
509      */
get24HourMode(final Context context)510     static boolean get24HourMode(final Context context) {
511         return android.text.format.DateFormat.is24HourFormat(context);
512     }
513 }
514