2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.deskclock;
19 import android.app.AlarmManager;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.ContentUris;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Parcel;
31 import android.provider.Settings;
32 import android.text.format.DateFormat;
34 import java.util.Calendar;
35 import java.text.DateFormatSymbols;
38 * The Alarms provider supplies info about Alarm Clock settings
42 // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It
43 // is a public action used in the manifest for receiving Alarm broadcasts
44 // from the alarm manager.
45 public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
47 // This is a private action used by the AlarmKlaxon to update the UI to
48 // show the alarm has been killed.
49 public static final String ALARM_KILLED = "alarm_killed";
51 // Extra in the ALARM_KILLED intent to indicate to the user how long the
52 // alarm played before being killed.
53 public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout";
55 // This string is used to indicate a silent alarm in the db.
56 public static final String ALARM_ALERT_SILENT = "silent";
58 // This intent is sent from the notification when the user cancels the
60 public static final String CANCEL_SNOOZE = "cancel_snooze";
62 // This string is used when passing an Alarm object through an intent.
63 public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm";
65 // This extra is the raw Alarm object data. It is used in the
66 // AlarmManagerService to avoid a ClassNotFoundException when filling in
68 public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw";
70 // This string is used to identify the alarm id passed to SetAlarm from the
72 public static final String ALARM_ID = "alarm_id";
74 final static String PREF_SNOOZE_ID = "snooze_id";
75 final static String PREF_SNOOZE_TIME = "snooze_time";
77 private final static String DM12 = "E h:mm aa";
78 private final static String DM24 = "E k:mm";
80 private final static String M12 = "h:mm aa";
81 // Shared with DigitalClock
82 final static String M24 = "kk:mm";
85 * Creates a new Alarm.
87 public static Uri addAlarm(ContentResolver contentResolver) {
88 ContentValues values = new ContentValues();
89 values.put(Alarm.Columns.HOUR, 8);
90 return contentResolver.insert(Alarm.Columns.CONTENT_URI, values);
94 * Removes an existing Alarm. If this alarm is snoozing, disables
95 * snooze. Sets next alert.
97 public static void deleteAlarm(
98 Context context, int alarmId) {
100 ContentResolver contentResolver = context.getContentResolver();
101 /* If alarm is snoozing, lose it */
102 disableSnoozeAlert(context, alarmId);
104 Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId);
105 contentResolver.delete(uri, "", null);
107 setNextAlert(context);
112 * @return cursor over all alarms
114 public static Cursor getAlarmsCursor(ContentResolver contentResolver) {
115 return contentResolver.query(
116 Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS,
117 null, null, Alarm.Columns.DEFAULT_SORT_ORDER);
120 // Private method to get a more limited set of alarms from the database.
121 private static Cursor getFilteredAlarmsCursor(
122 ContentResolver contentResolver) {
123 return contentResolver.query(Alarm.Columns.CONTENT_URI,
124 Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED,
129 * Return an Alarm object representing the alarm id in the database.
130 * Returns null if no alarm exists.
132 public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) {
133 Cursor cursor = contentResolver.query(
134 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId),
135 Alarm.Columns.ALARM_QUERY_COLUMNS,
138 if (cursor != null) {
139 if (cursor.moveToFirst()) {
140 alarm = new Alarm(cursor);
149 * A convenience method to set an alarm in the Alarms
152 * @param id corresponds to the _id column
153 * @param enabled corresponds to the ENABLED column
154 * @param hour corresponds to the HOUR column
155 * @param minutes corresponds to the MINUTES column
156 * @param daysOfWeek corresponds to the DAYS_OF_WEEK column
157 * @param time corresponds to the ALARM_TIME column
158 * @param vibrate corresponds to the VIBRATE column
159 * @param message corresponds to the MESSAGE column
160 * @param alert corresponds to the ALERT column
161 * @return Time when the alarm will fire.
163 public static long setAlarm(
164 Context context, int id, boolean enabled, int hour, int minutes,
165 Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message,
168 ContentValues values = new ContentValues(8);
169 ContentResolver resolver = context.getContentResolver();
170 // Set the alarm_time value if this alarm does not repeat. This will be
171 // used later to disable expired alarms.
173 if (!daysOfWeek.isRepeatSet()) {
174 time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
178 "** setAlarm * idx " + id + " hour " + hour + " minutes " +
179 minutes + " enabled " + enabled + " time " + time);
181 values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
182 values.put(Alarm.Columns.HOUR, hour);
183 values.put(Alarm.Columns.MINUTES, minutes);
184 values.put(Alarm.Columns.ALARM_TIME, time);
185 values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded());
186 values.put(Alarm.Columns.VIBRATE, vibrate);
187 values.put(Alarm.Columns.MESSAGE, message);
188 values.put(Alarm.Columns.ALERT, alert);
189 resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id),
193 calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
196 // If this alarm fires before the next snooze, clear the snooze to
197 // enable this alarm.
198 SharedPreferences prefs = context.getSharedPreferences(
199 AlarmClock.PREFERENCES, 0);
200 long snoozeTime = prefs.getLong(PREF_SNOOZE_TIME, 0);
201 if (timeInMillis < snoozeTime) {
202 clearSnoozePreference(context, prefs);
206 setNextAlert(context);
212 * A convenience method to enable or disable an alarm.
214 * @param id corresponds to the _id column
215 * @param enabled corresponds to the ENABLED column
218 public static void enableAlarm(
219 final Context context, final int id, boolean enabled) {
220 enableAlarmInternal(context, id, enabled);
221 setNextAlert(context);
224 private static void enableAlarmInternal(final Context context,
225 final int id, boolean enabled) {
226 enableAlarmInternal(context, getAlarm(context.getContentResolver(), id),
230 private static void enableAlarmInternal(final Context context,
231 final Alarm alarm, boolean enabled) {
235 ContentResolver resolver = context.getContentResolver();
237 ContentValues values = new ContentValues(2);
238 values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
240 // If we are enabling the alarm, calculate alarm time since the time
241 // value in Alarm may be old.
244 if (!alarm.daysOfWeek.isRepeatSet()) {
245 time = calculateAlarm(alarm.hour, alarm.minutes,
246 alarm.daysOfWeek).getTimeInMillis();
248 values.put(Alarm.Columns.ALARM_TIME, time);
250 // Clear the snooze if the id matches.
251 disableSnoozeAlert(context, alarm.id);
254 resolver.update(ContentUris.withAppendedId(
255 Alarm.Columns.CONTENT_URI, alarm.id), values, null, null);
258 public static Alarm calculateNextAlert(final Context context) {
260 long minTime = Long.MAX_VALUE;
261 long now = System.currentTimeMillis();
262 Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver());
263 if (cursor != null) {
264 if (cursor.moveToFirst()) {
266 Alarm a = new Alarm(cursor);
267 // A time of 0 indicates this is a repeating alarm, so
268 // calculate the time to get the next alert.
270 a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek)
272 } else if (a.time < now) {
273 // Expired alarm, disable it and move along.
274 enableAlarmInternal(context, a, false);
277 if (a.time < minTime) {
281 } while (cursor.moveToNext());
289 * Disables non-repeating alarms that have passed. Called at
292 public static void disableExpiredAlarms(final Context context) {
293 Cursor cur = getFilteredAlarmsCursor(context.getContentResolver());
294 long now = System.currentTimeMillis();
296 if (cur.moveToFirst()) {
298 Alarm alarm = new Alarm(cur);
299 // A time of 0 means this alarm repeats. If the time is
300 // non-zero, check if the time is before now.
301 if (alarm.time != 0 && alarm.time < now) {
303 Log.v("** DISABLE " + alarm.id + " now " + now +" set "
306 enableAlarmInternal(context, alarm, false);
308 } while (cur.moveToNext());
314 * Called at system startup, on time/timezone change, and whenever
315 * the user changes alarm settings. Activates snooze if set,
316 * otherwise loads all alarms, activates next alert.
318 public static void setNextAlert(final Context context) {
319 if (!enableSnoozeAlert(context)) {
320 Alarm alarm = calculateNextAlert(context);
322 enableAlert(context, alarm, alarm.time);
324 disableAlert(context);
330 * Sets alert in AlarmManger and StatusBar. This is what will
331 * actually launch the alert when the alarm triggers.
333 * @param alarm Alarm.
334 * @param atTimeInMillis milliseconds since epoch
336 private static void enableAlert(Context context, final Alarm alarm,
337 final long atTimeInMillis) {
338 AlarmManager am = (AlarmManager)
339 context.getSystemService(Context.ALARM_SERVICE);
342 Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis);
345 Intent intent = new Intent(ALARM_ALERT_ACTION);
347 // XXX: This is a slight hack to avoid an exception in the remote
348 // AlarmManagerService process. The AlarmManager adds extra data to
349 // this Intent which causes it to inflate. Since the remote process
350 // does not know about the Alarm class, it throws a
351 // ClassNotFoundException.
353 // To avoid this, we marshall the data ourselves and then parcel a plain
354 // byte[] array. The AlarmReceiver class knows to build the Alarm
355 // object from the byte[] array.
356 Parcel out = Parcel.obtain();
357 alarm.writeToParcel(out, 0);
358 out.setDataPosition(0);
359 intent.putExtra(ALARM_RAW_DATA, out.marshall());
361 PendingIntent sender = PendingIntent.getBroadcast(
362 context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
364 am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
366 setStatusBarIcon(context, true);
368 Calendar c = Calendar.getInstance();
369 c.setTime(new java.util.Date(atTimeInMillis));
370 String timeString = formatDayAndTime(context, c);
371 saveNextAlarm(context, timeString);
375 * Disables alert in AlarmManger and StatusBar.
377 * @param id Alarm ID.
379 static void disableAlert(Context context) {
380 AlarmManager am = (AlarmManager)
381 context.getSystemService(Context.ALARM_SERVICE);
382 PendingIntent sender = PendingIntent.getBroadcast(
383 context, 0, new Intent(ALARM_ALERT_ACTION),
384 PendingIntent.FLAG_CANCEL_CURRENT);
386 setStatusBarIcon(context, false);
387 saveNextAlarm(context, "");
390 static void saveSnoozeAlert(final Context context, final int id,
392 SharedPreferences prefs = context.getSharedPreferences(
393 AlarmClock.PREFERENCES, 0);
395 clearSnoozePreference(context, prefs);
397 SharedPreferences.Editor ed = prefs.edit();
398 ed.putInt(PREF_SNOOZE_ID, id);
399 ed.putLong(PREF_SNOOZE_TIME, time);
402 // Set the next alert after updating the snooze.
403 setNextAlert(context);
407 * Disable the snooze alert if the given id matches the snooze id.
409 static void disableSnoozeAlert(final Context context, final int id) {
410 SharedPreferences prefs = context.getSharedPreferences(
411 AlarmClock.PREFERENCES, 0);
412 int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1);
413 if (snoozeId == -1) {
414 // No snooze set, do nothing.
416 } else if (snoozeId == id) {
417 // This is the same id so clear the shared prefs.
418 clearSnoozePreference(context, prefs);
422 // Helper to remove the snooze preference. Do not use clear because that
423 // will erase the clock preferences. Also clear the snooze notification in
425 private static void clearSnoozePreference(final Context context,
426 final SharedPreferences prefs) {
427 final int alarmId = prefs.getInt(PREF_SNOOZE_ID, -1);
429 NotificationManager nm = (NotificationManager)
430 context.getSystemService(Context.NOTIFICATION_SERVICE);
434 final SharedPreferences.Editor ed = prefs.edit();
435 ed.remove(PREF_SNOOZE_ID);
436 ed.remove(PREF_SNOOZE_TIME);
441 * If there is a snooze set, enable it in AlarmManager
442 * @return true if snooze is set
444 private static boolean enableSnoozeAlert(final Context context) {
445 SharedPreferences prefs = context.getSharedPreferences(
446 AlarmClock.PREFERENCES, 0);
448 int id = prefs.getInt(PREF_SNOOZE_ID, -1);
452 long time = prefs.getLong(PREF_SNOOZE_TIME, -1);
454 // Get the alarm from the db.
455 final Alarm alarm = getAlarm(context.getContentResolver(), id);
459 // The time in the database is either 0 (repeating) or a specific time
460 // for a non-repeating alarm. Update this value so the AlarmReceiver
461 // has the right time to compare.
464 enableAlert(context, alarm, time);
469 * Tells the StatusBar whether the alarm is enabled or disabled
471 private static void setStatusBarIcon(Context context, boolean enabled) {
472 Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED);
473 alarmChanged.putExtra("alarmSet", enabled);
474 context.sendBroadcast(alarmChanged);
478 * Given an alarm in hours and minutes, return a time suitable for
479 * setting in AlarmManager.
480 * @param hour Always in 24 hour 0-23
482 * @param daysOfWeek 0-59
484 static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) {
487 Calendar c = Calendar.getInstance();
488 c.setTimeInMillis(System.currentTimeMillis());
490 int nowHour = c.get(Calendar.HOUR_OF_DAY);
491 int nowMinute = c.get(Calendar.MINUTE);
493 // if alarm is behind current time, advance one day
494 if (hour < nowHour ||
495 hour == nowHour && minute <= nowMinute) {
496 c.add(Calendar.DAY_OF_YEAR, 1);
498 c.set(Calendar.HOUR_OF_DAY, hour);
499 c.set(Calendar.MINUTE, minute);
500 c.set(Calendar.SECOND, 0);
501 c.set(Calendar.MILLISECOND, 0);
503 int addDays = daysOfWeek.getNextAlarm(c);
504 /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour +
505 " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " +
507 if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays);
511 static String formatTime(final Context context, int hour, int minute,
512 Alarm.DaysOfWeek daysOfWeek) {
513 Calendar c = calculateAlarm(hour, minute, daysOfWeek);
514 return formatTime(context, c);
517 /* used by AlarmAlert */
518 static String formatTime(final Context context, Calendar c) {
519 String format = get24HourMode(context) ? M24 : M12;
520 return (c == null) ? "" : (String)DateFormat.format(format, c);
524 * Shows day and time -- used for lock screen
526 private static String formatDayAndTime(final Context context, Calendar c) {
527 String format = get24HourMode(context) ? DM24 : DM12;
528 return (c == null) ? "" : (String)DateFormat.format(format, c);
532 * Save time of the next alarm, as a formatted string, into the system
533 * settings so those who care can make use of it.
535 static void saveNextAlarm(final Context context, String timeString) {
536 Settings.System.putString(context.getContentResolver(),
537 Settings.System.NEXT_ALARM_FORMATTED,
542 * @return true if clock is set to 24-hour mode
544 static boolean get24HourMode(final Context context) {
545 return android.text.format.DateFormat.is24HourFormat(context);