OSDN Git Service

Fix the build.
[android-x86/packages-apps-DeskClock.git] / src / com / android / alarmclock / Alarms.java
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      */
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      */
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      */
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.
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      */
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      */
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
203     public static void enableAlarm(
204             final Context context, final int id, boolean enabled) {
205         enableAlarmInternal(context, id, enabled);
206         setNextAlert(context);
207     }
208
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
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
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      */
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      */
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      */
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      */
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
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      */
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.
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      */
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      */
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      */
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
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 */
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      */
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      */
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      */
510     static boolean get24HourMode(final Context context) {
511         return android.text.format.DateFormat.is24HourFormat(context);
512     }
513 }