OSDN Git Service

Huge rewrite of the AlarmClock to play the Alarm in a service.
authorPatrick Scott <phanna@android.com>
Fri, 26 Jun 2009 18:52:56 +0000 (14:52 -0400)
committerPatrick Scott <phanna@android.com>
Fri, 26 Jun 2009 20:29:13 +0000 (16:29 -0400)
The AlarmKlaxon has been converted to a service that plays the alarm and
vibrates the device. The AlarmAlert now just shows the UI for the alarm and
allows the user to snooze or dismiss the alarm. The snooze button must be
pressed in order to snooze the alarm. Volume and Camera buttons dismiss the
alarm while other buttons have their original behavior.

Each alarm triggers a notification that the alarm has fired. This allows another
activity (say, the Calendar alert #1908616) to play on top of the AlarmAlert.
The AlarmKlaxon service will continue to play even though the alert has been
dismissed. The user can get back to the UI through the notification.

If the user snoozes the alarm, the notification reflects that choice (#1691034)
and allows the user to cancel the snooze by clicking the notification. The
snoozed alarm takes priority over any other alarm (#1693155) so that it will
play unless the notification is clicked.

The database interaction has also been rewritten to use a Parcelable Alarm class
for sending and receiving the alarm data in a much simpler manner. This allows
for fewer database lookups since each activity no longer has to lookup the alarm
info.

The alarm silenced text has been removed from the AlarmAlert UI and moved to the
notification area. When an alarm is killed, the alert is dismissed and the
notification reflects the state. Clicking the notification launches the SetAlarm
activity so the user can see which alarm was killed.

30 files changed:
AndroidManifest.xml
res/drawable/stat_notify_alarm.png [new file with mode: 0644]
res/layout/alarm_alert.xml
res/values-cs/strings.xml
res/values-de/strings.xml
res/values-es-rUS/strings.xml
res/values-es/strings.xml
res/values-fr/strings.xml
res/values-it/strings.xml
res/values-ja/strings.xml
res/values-ko/strings.xml
res/values-nb/strings.xml
res/values-nl/strings.xml
res/values-pl/strings.xml
res/values-pt/strings.xml
res/values-ru/strings.xml
res/values-zh-rCN/strings.xml
res/values-zh-rTW/strings.xml
res/values/strings.xml
src/com/android/alarmclock/Alarm.java [new file with mode: 0644]
src/com/android/alarmclock/AlarmAlert.java
src/com/android/alarmclock/AlarmAlertWakeLock.java
src/com/android/alarmclock/AlarmClock.java
src/com/android/alarmclock/AlarmInitReceiver.java
src/com/android/alarmclock/AlarmKlaxon.java
src/com/android/alarmclock/AlarmProvider.java
src/com/android/alarmclock/AlarmReceiver.java
src/com/android/alarmclock/Alarms.java
src/com/android/alarmclock/RepeatPreference.java
src/com/android/alarmclock/SetAlarm.java

index 3278226..f8bd0fe 100644 (file)
         <receiver android:name="AlarmReceiver">
             <intent-filter>
                <action android:name="com.android.alarmclock.ALARM_ALERT" />
+               <action android:name="alarm_killed" />
+               <action android:name="cancel_snooze" />
             </intent-filter>
         </receiver>
 
+        <!-- This service receives the same intent as AlarmReceiver but it does
+             not respond to the same broadcast. The AlarmReceiver will receive
+             the alert broadcast and will start this service with the same
+             intent. The service plays the alarm alert and vibrates the device.
+             This allows the alert to continue playing even if another activity
+             causes the AlarmAlert activity to pause. -->
+        <service android:name="AlarmKlaxon">
+            <intent-filter>
+                <action android:name="com.android.alarmclock.ALARM_ALERT" />
+            </intent-filter>
+        </service>
+
         <receiver android:name="AlarmInitReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/res/drawable/stat_notify_alarm.png b/res/drawable/stat_notify_alarm.png
new file mode 100644 (file)
index 0000000..6012575
Binary files /dev/null and b/res/drawable/stat_notify_alarm.png differ
index 5db840e..9964c08 100644 (file)
 
         </LinearLayout>
 
-        <TextView android:id="@+id/silencedText"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingTop="2dp"
-            android:paddingBottom="2dp"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:visibility="gone"
-            android:gravity="center"
-            android:textColor="@color/red"/>
-
     </LinearLayout>
+
 </LinearLayout>
index 5e68d4a..ea3e166 100644 (file)
     <string name="alarm_repeat">"Opakovat"</string>
     <string name="alert">"Vyzváněcí tón"</string>
     <string name="time">"Čas"</string>
-    <string name="alert_title">"Upozornění"</string>
     <string name="alarm_alert_dismiss_text">"Zavřít"</string>
     <string name="alarm_alert_alert_silenced">"Upozornění ztišeno po uplynutí <xliff:g id="MINUTES">%d</xliff:g> min."</string>
     <string name="alarm_alert_snooze_text">"Odložit"</string>
     <string name="alarm_alert_snooze_set">"Upozornění odloženo o <xliff:g id="MINUTES">%d</xliff:g> min."</string>
-    <string name="alarm_alert_snooze_not_set">"Upozornění nebylo odloženo – další upozornění v <xliff:g id="TIME">%s</xliff:g>."</string>
     <string name="alarm_set">"Tento budík bude aktivován za: <xliff:g id="TIME_DELTA">%s</xliff:g>."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 den"</string>
index 5d7a504..8bd5e07 100644 (file)
     <string name="alarm_repeat">"Wiederholen"</string>
     <string name="alert">"Klingelton"</string>
     <string name="time">"Uhrzeit"</string>
-    <string name="alert_title">"Wecker"</string>
     <string name="alarm_alert_dismiss_text">"Verwerfen"</string>
     <string name="alarm_alert_alert_silenced">"Der Wecker verstummte nach <xliff:g id="MINUTES">%d</xliff:g> Minuten"</string>
     <string name="alarm_alert_snooze_text">"Snooze-Funktion"</string>
     <string name="alarm_alert_snooze_set">"Snooze-Funktion aktiviert für <xliff:g id="MINUTES">%d</xliff:g> Minuten"</string>
-    <string name="alarm_alert_snooze_not_set">"Snooze-Funktion nicht aktiviert - nächster Wecker klingelt <xliff:g id="TIME">%s</xliff:g>."</string>
     <string name="alarm_set">"Dieser Wecker klingelt in <xliff:g id="TIME_DELTA">%s</xliff:g>."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 Tag"</string>
index ed43531..7dc26fd 100644 (file)
     <string name="alarm_repeat">"Repetir"</string>
     <string name="alert">"Timbre"</string>
     <string name="time">"Hora"</string>
-    <string name="alert_title">"Alarma"</string>
     <string name="alarm_alert_dismiss_text">"Descartar"</string>
     <string name="alarm_alert_alert_silenced">"Alarma silenciada después de <xliff:g id="MINUTES">%d</xliff:g> minutos"</string>
     <string name="alarm_alert_snooze_text">"Recurrente"</string>
     <string name="alarm_alert_snooze_set">"Recurrente a los <xliff:g id="MINUTES">%d</xliff:g> minutos."</string>
-    <string name="alarm_alert_snooze_not_set">"Alarma recurrente no activada. Próxima alarma fijada a las <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Esta alarma se activará en <xliff:g id="TIME_DELTA">%s</xliff:g> a partir de ahora."</string>
     <string name="combiner">"Segmento <xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"un día"</string>
index d6019de..56a7782 100644 (file)
     <string name="alarm_repeat">"Repetir"</string>
     <string name="alert">"Tono"</string>
     <string name="time">"Hora"</string>
-    <string name="alert_title">"Alarma"</string>
     <string name="alarm_alert_dismiss_text">"Descartar"</string>
     <string name="alarm_alert_alert_silenced">"La alarma se ha silenciado después de <xliff:g id="MINUTES">%d</xliff:g> minutos."</string>
     <string name="alarm_alert_snooze_text">"Posponer"</string>
     <string name="alarm_alert_snooze_set">"La alarma volverá a sonar en <xliff:g id="MINUTES">%d</xliff:g> minutos."</string>
-    <string name="alarm_alert_snooze_not_set">"No se ha pospuesto la alarma, ya que la siguiente alarma sonará a las <xliff:g id="TIME">%s</xliff:g>."</string>
     <string name="alarm_set">"La alarma sonará en <xliff:g id="TIME_DELTA">%s</xliff:g>."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 día"</string>
index edaf759..1b73859 100644 (file)
     <string name="alarm_repeat">"Répéter"</string>
     <string name="alert">"Sonnerie"</string>
     <string name="time">"Heure"</string>
-    <string name="alert_title">"Alarme"</string>
     <string name="alarm_alert_dismiss_text">"Quitter"</string>
     <string name="alarm_alert_alert_silenced">"Alarme interrompue après <xliff:g id="MINUTES">%d</xliff:g> minutes"</string>
     <string name="alarm_alert_snooze_text">"Répéter"</string>
     <string name="alarm_alert_snooze_set">"Répétition dans <xliff:g id="MINUTES">%d</xliff:g> minutes."</string>
-    <string name="alarm_alert_snooze_not_set">"Répétition non activée : prochaine alarme à <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Prochaine alarme dans <xliff:g id="TIME_DELTA">%s</xliff:g>."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 jour"</string>
index 705e50f..c24b420 100644 (file)
     <string name="alarm_repeat">"Ripeti"</string>
     <string name="alert">"Suoneria"</string>
     <string name="time">"Ora"</string>
-    <string name="alert_title">"Allarme"</string>
     <string name="alarm_alert_dismiss_text">"Spegni"</string>
     <string name="alarm_alert_alert_silenced">"Tono allarme disattivato dopo <xliff:g id="MINUTES">%d</xliff:g> minuti"</string>
     <string name="alarm_alert_snooze_text">"Posponi"</string>
     <string name="alarm_alert_snooze_set">"Sospensione per <xliff:g id="MINUTES">%d</xliff:g> minuti."</string>
-    <string name="alarm_alert_snooze_not_set">"Sospensione non impostata. Prossimo allarme alle <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"L\'allarme sarà attivato fra <xliff:g id="TIME_DELTA">%s</xliff:g>."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 giorno"</string>
index 3e4bb7c..c1f9b02 100644 (file)
     <string name="alarm_repeat">"繰り返し"</string>
     <string name="alert">"アラーム音"</string>
     <string name="time">"時刻"</string>
-    <string name="alert_title">"アラーム"</string>
     <string name="alarm_alert_dismiss_text">"停止"</string>
     <string name="alarm_alert_alert_silenced">"アラームは<xliff:g id="MINUTES">%d</xliff:g>分間鳴って止まりました"</string>
     <string name="alarm_alert_snooze_text">"スヌーズ"</string>
     <string name="alarm_alert_snooze_set">"<xliff:g id="MINUTES">%d</xliff:g>分後に再通知します"</string>
-    <string name="alarm_alert_snooze_not_set">"スヌーズにできません: <xliff:g id="TIME">%s</xliff:g>に次のアラームが設定されています"</string>
     <string name="alarm_set">"今から<xliff:g id="TIME_DELTA">%s</xliff:g>後に設定しました"</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1日"</string>
index 3161d01..c363cf0 100644 (file)
     <string name="alarm_repeat">"반복"</string>
     <string name="alert">"벨소리"</string>
     <string name="time">"시간"</string>
-    <string name="alert_title">"알람"</string>
     <string name="alarm_alert_dismiss_text">"해제"</string>
     <string name="alarm_alert_alert_silenced">"<xliff:g id="MINUTES">%d</xliff:g>분 후에 알람이 꺼집니다."</string>
     <string name="alarm_alert_snooze_text">"스누즈"</string>
     <string name="alarm_alert_snooze_set">"<xliff:g id="MINUTES">%d</xliff:g>분 동안 스누즈"</string>
-    <string name="alarm_alert_snooze_not_set">"스누즈 설정되지 않음. <xliff:g id="TIME">%s</xliff:g>에 다음 알람이 설정되어 있음."</string>
     <string name="alarm_set">"이 알람은 지금부터 <xliff:g id="TIME_DELTA">%s</xliff:g> 동안 설정됩니다."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1일"</string>
index 594c4d6..ebe9e72 100644 (file)
     <string name="alarm_repeat">"Gjenta"</string>
     <string name="alert">"Ringetone"</string>
     <string name="time">"Tidspunkt"</string>
-    <string name="alert_title">"Alarm"</string>
     <string name="alarm_alert_dismiss_text">"OK"</string>
     <string name="alarm_alert_alert_silenced">"Alarmen ble stilnet etter <xliff:g id="MINUTES">%d</xliff:g> minutter"</string>
     <string name="alarm_alert_snooze_text">"Slumre"</string>
     <string name="alarm_alert_snooze_set">"Slumrer i <xliff:g id="MINUTES">%d</xliff:g> minutter."</string>
-    <string name="alarm_alert_snooze_not_set">"Slumrer ikke -- neste alarm vil gå av <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Alarmen vil gå av <xliff:g id="TIME_DELTA">%s</xliff:g> fra nå."</string>
     <!-- no translation found for combiner (3170916241487451546) -->
     <skip />
index a6475a9..4f451ca 100644 (file)
     <string name="alarm_repeat">"Herhalen"</string>
     <string name="alert">"Beltoon"</string>
     <string name="time">"Tijd"</string>
-    <string name="alert_title">"Alarm"</string>
     <string name="alarm_alert_dismiss_text">"Negeren"</string>
     <string name="alarm_alert_alert_silenced">"Alarm gaat uit na <xliff:g id="MINUTES">%d</xliff:g> minuten"</string>
     <string name="alarm_alert_snooze_text">"Snooze"</string>
     <string name="alarm_alert_snooze_set">"Snooze is ingesteld op <xliff:g id="MINUTES">%d</xliff:g> minuten."</string>
-    <string name="alarm_alert_snooze_not_set">"Snooze is niet ingesteld - volgende alarm is ingesteld op <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Dit alarm is ingesteld op <xliff:g id="TIME_DELTA">%s</xliff:g> vanaf nu."</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 dag"</string>
index dc30b14..75cf97c 100644 (file)
     <string name="alarm_repeat">"Powtarzanie"</string>
     <string name="alert">"Dzwonek"</string>
     <string name="time">"Godzina"</string>
-    <string name="alert_title">"Alarm"</string>
     <string name="alarm_alert_dismiss_text">"Zamknij"</string>
     <string name="alarm_alert_alert_silenced">"Alarm jest wyciszany po <xliff:g id="MINUTES">%d</xliff:g> min."</string>
     <string name="alarm_alert_snooze_text">"Drzemka"</string>
     <string name="alarm_alert_snooze_set">"Drzemka przez <xliff:g id="MINUTES">%d</xliff:g> min."</string>
-    <string name="alarm_alert_snooze_not_set">"Nie ustawiono drzemki – następny alarm: <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Ustawiony alarm zadzwoni za <xliff:g id="TIME_DELTA">%s</xliff:g>"</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 dzień"</string>
index fad7095..b30c9c1 100644 (file)
     <string name="alarm_repeat">"Repetir"</string>
     <string name="alert">"Ringtone"</string>
     <string name="time">"Hora"</string>
-    <string name="alert_title">"Alarme"</string>
     <string name="alarm_alert_dismiss_text">"Descartar"</string>
     <string name="alarm_alert_alert_silenced">"Alarme silenciado após <xliff:g id="MINUTES">%d</xliff:g> minutos"</string>
     <string name="alarm_alert_snooze_text">"Soneca"</string>
     <string name="alarm_alert_snooze_set">"Soneca de <xliff:g id="MINUTES">%d</xliff:g> minutos."</string>
-    <string name="alarm_alert_snooze_not_set">"Soneca não definida - próximo alarme definido para <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Este alarme está definido para <xliff:g id="TIME_DELTA">%s</xliff:g> a partir de agora."</string>
     <!-- no translation found for combiner (3170916241487451546) -->
     <skip />
index 46a378a..d28a075 100644 (file)
     <string name="alarm_repeat">"Повтор"</string>
     <string name="alert">"Мелодия звонка"</string>
     <string name="time">"Время"</string>
-    <string name="alert_title">"Будильник"</string>
     <string name="alarm_alert_dismiss_text">"Закрыть"</string>
     <string name="alarm_alert_alert_silenced">"Будильник отключается через <xliff:g id="MINUTES">%d</xliff:g> минут(ы)"</string>
     <string name="alarm_alert_snooze_text">"Отсрочка"</string>
     <string name="alarm_alert_snooze_set">"Отсрочка на <xliff:g id="MINUTES">%d</xliff:g> минут(ы)."</string>
-    <string name="alarm_alert_snooze_not_set">"Отсрочка не установлена – следующий будильник установлен на <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"Будильник прозвонит через <xliff:g id="TIME_DELTA">%s</xliff:g>"</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 день"</string>
index 9944ee4..453e925 100644 (file)
     <string name="alarm_repeat">"重复"</string>
     <string name="alert">"铃声"</string>
     <string name="time">"时间"</string>
-    <string name="alert_title">"闹钟"</string>
     <string name="alarm_alert_dismiss_text">"取消"</string>
     <string name="alarm_alert_alert_silenced">"闹钟在 <xliff:g id="MINUTES">%d</xliff:g> 分钟后静音"</string>
     <string name="alarm_alert_snooze_text">"再响"</string>
     <string name="alarm_alert_snooze_set">"<xliff:g id="MINUTES">%d</xliff:g> 分钟后再响。"</string>
-    <string name="alarm_alert_snooze_not_set">"未设置再响 - 下次闹钟设置在 <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"该闹钟设置为从现在起 <xliff:g id="TIME_DELTA">%s</xliff:g> 后再提醒。"</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 天"</string>
index 2e852bc..e92d492 100644 (file)
     <string name="alarm_repeat">"重複"</string>
     <string name="alert">"鈴聲"</string>
     <string name="time">"時間"</string>
-    <string name="alert_title">"鬧鐘"</string>
     <string name="alarm_alert_dismiss_text">"關閉"</string>
     <string name="alarm_alert_alert_silenced">"鬧鐘 <xliff:g id="MINUTES">%d</xliff:g> 分鐘後靜音"</string>
     <string name="alarm_alert_snooze_text">"貪睡"</string>
     <string name="alarm_alert_snooze_set">"再貪睡 <xliff:g id="MINUTES">%d</xliff:g> 分鐘。"</string>
-    <string name="alarm_alert_snooze_not_set">"未設定貪睡鬧鐘 -- 下一次鬧鐘時間為 <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="alarm_set">"還有 <xliff:g id="TIME_DELTA">%s</xliff:g> 會啟動鬧鐘。"</string>
     <string name="combiner">"<xliff:g id="XXX_0">%1$s</xliff:g><xliff:g id="XXX_1">%2$s</xliff:g><xliff:g id="XXX_2">%3$s</xliff:g><xliff:g id="XXX_3">%4$s</xliff:g><xliff:g id="XXX_4">%5$s</xliff:g>"</string>
     <string name="day">"1 天"</string>
index 6cc24d1..852d500 100644 (file)
     <!-- Setting labels on Set alarm screen: Set time  -->
     <string name="time">Time</string>
 
-
-    <!-- Title of the alarm alert -->
-    <string name="alert_title">Alarm</string>
-    
     <!-- Button labels on the alarm dialog: Dismiss -->
     <string name="alarm_alert_dismiss_text">Dismiss</string>
 
          dialog. Says the alarm will snooze for xxx minutes.  -->
     <string name="alarm_alert_snooze_set">Snoozing for <xliff:g id="minutes">%d</xliff:g> minutes.</string>
 
-    <!-- Toast that appears after Alarm is snoozed from the Alarm
-         dialog. Shown if snooze cannot be set because the next alarm
-         would fire before the snooze alrm.  "Snooze not set - next
-         alarm set for xxx."  -->
-    <string name="alarm_alert_snooze_not_set">Snooze not set -- next alarm set for <xliff:g id="time">%s</xliff:g></string>
-
-
     <!-- Alarm confirmation toast: Describes how long from now until
          alarm fires -->
     <string name="alarm_set">This alarm is set for <xliff:g id="time_delta">%s</xliff:g> from now.</string>
 
     <!-- Summary for the alarm preference when silent is chosen. -->
     <string name="silent_alarm_summary">Silent</string>
+
+    <!-- Text to display in the small text of the notification -->
+    <string name="alarm_notify_text">Select to snooze or dismiss this alarm.</string>
+
+    <!-- Text to display in the notification ticker and label -->
+    <string name="alarm_notify_snooze_label"><xliff:g id="label">%s</xliff:g>
+      (snoozed)</string>
+
+    <!-- Text to display in the notification when the alarm has been snoozed -->
+    <string name="alarm_notify_snooze_text">Alarm set for <xliff:g
+        id="time">%s</xliff:g>. Click to cancel.</string>
 </resources>
 
 
diff --git a/src/com/android/alarmclock/Alarm.java b/src/com/android/alarmclock/Alarm.java
new file mode 100644 (file)
index 0000000..350b7b4
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.alarmclock;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.BaseColumns;
+import android.text.format.DateFormat;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+
+public final class Alarm implements Parcelable {
+
+    //////////////////////////////
+    // Parcelable apis
+    //////////////////////////////
+    public static final Parcelable.Creator<Alarm> CREATOR
+            = new Parcelable.Creator<Alarm>() {
+                public Alarm createFromParcel(Parcel p) {
+                    return new Alarm(p);
+                }
+
+                public Alarm[] newArray(int size) {
+                    return new Alarm[size];
+                }
+            };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel p, int flags) {
+        p.writeInt(id);
+        p.writeInt(enabled ? 1 : 0);
+        p.writeInt(hour);
+        p.writeInt(minutes);
+        p.writeInt(daysOfWeek.getCoded());
+        p.writeLong(time);
+        p.writeInt(vibrate ? 1 : 0);
+        p.writeString(label);
+        p.writeParcelable(alert, flags);
+        p.writeInt(silent ? 1 : 0);
+    }
+    //////////////////////////////
+    // end Parcelable apis
+    //////////////////////////////
+
+    //////////////////////////////
+    // Column definitions
+    //////////////////////////////
+    public static class Columns implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://com.android.alarmclock/alarm");
+
+        /**
+         * Hour in 24-hour localtime 0 - 23.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String HOUR = "hour";
+
+        /**
+         * Minutes in localtime 0 - 59
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MINUTES = "minutes";
+
+        /**
+         * Days of week coded as integer
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DAYS_OF_WEEK = "daysofweek";
+
+        /**
+         * Alarm time in UTC milliseconds from the epoch.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ALARM_TIME = "alarmtime";
+
+        /**
+         * True if alarm is active
+         * <P>Type: BOOLEAN</P>
+         */
+        public static final String ENABLED = "enabled";
+
+        /**
+         * True if alarm should vibrate
+         * <P>Type: BOOLEAN</P>
+         */
+        public static final String VIBRATE = "vibrate";
+
+        /**
+         * Message to show when alarm triggers
+         * Note: not currently used
+         * <P>Type: STRING</P>
+         */
+        public static final String MESSAGE = "message";
+
+        /**
+         * Audio alert to play when alarm triggers
+         * <P>Type: STRING</P>
+         */
+        public static final String ALERT = "alert";
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = _ID + " ASC";
+
+        // Used when filtering enabled alarms.
+        public static final String WHERE_ENABLED = ENABLED + "=1";
+
+        static final String[] ALARM_QUERY_COLUMNS = {
+            _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME,
+            ENABLED, VIBRATE, MESSAGE, ALERT };
+
+        /**
+         * These save calls to cursor.getColumnIndexOrThrow()
+         * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
+         */
+        public static final int ALARM_ID_INDEX = 0;
+        public static final int ALARM_HOUR_INDEX = 1;
+        public static final int ALARM_MINUTES_INDEX = 2;
+        public static final int ALARM_DAYS_OF_WEEK_INDEX = 3;
+        public static final int ALARM_TIME_INDEX = 4;
+        public static final int ALARM_ENABLED_INDEX = 5;
+        public static final int ALARM_VIBRATE_INDEX = 6;
+        public static final int ALARM_MESSAGE_INDEX = 7;
+        public static final int ALARM_ALERT_INDEX = 8;
+    }
+    //////////////////////////////
+    // End column definitions
+    //////////////////////////////
+
+    // Public fields
+    public int        id;
+    public boolean    enabled;
+    public int        hour;
+    public int        minutes;
+    public DaysOfWeek daysOfWeek;
+    public long       time;
+    public boolean    vibrate;
+    public String     label;
+    public Uri        alert;
+    public boolean    silent;
+
+    public Alarm(Cursor c) {
+        id = c.getInt(Columns.ALARM_ID_INDEX);
+        enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1;
+        hour = c.getInt(Columns.ALARM_HOUR_INDEX);
+        minutes = c.getInt(Columns.ALARM_MINUTES_INDEX);
+        daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX));
+        time = c.getLong(Columns.ALARM_TIME_INDEX);
+        vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1;
+        label = c.getString(Columns.ALARM_MESSAGE_INDEX);
+        String alertString = c.getString(Columns.ALARM_ALERT_INDEX);
+        if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) {
+            if (Log.LOGV) {
+                Log.v("Alarm is marked as silent");
+            }
+            silent = true;
+        } else {
+            if (alertString != null && alertString.length() != 0) {
+                alert = Uri.parse(alertString);
+            }
+
+            // If the database alert is null or it failed to parse, use the
+            // default alert.
+            if (alert == null) {
+                alert = RingtoneManager.getDefaultUri(
+                        RingtoneManager.TYPE_ALARM);
+            }
+        }
+    }
+
+    public Alarm(Parcel p) {
+        id = p.readInt();
+        enabled = p.readInt() == 1;
+        hour = p.readInt();
+        minutes = p.readInt();
+        daysOfWeek = new DaysOfWeek(p.readInt());
+        time = p.readLong();
+        vibrate = p.readInt() == 1;
+        label = p.readString();
+        alert = (Uri) p.readParcelable(null);
+        silent = p.readInt() == 1;
+    }
+
+    public String getLabelOrDefault(Context context) {
+        if (label == null || label.length() == 0) {
+            return context.getString(R.string.default_label);
+        }
+        return label;
+    }
+
+    /*
+     * Days of week code as a single int.
+     * 0x00: no day
+     * 0x01: Monday
+     * 0x02: Tuesday
+     * 0x04: Wednesday
+     * 0x08: Thursday
+     * 0x10: Friday
+     * 0x20: Saturday
+     * 0x40: Sunday
+     */
+    static final class DaysOfWeek {
+
+        private static int[] DAY_MAP = new int[] {
+            Calendar.MONDAY,
+            Calendar.TUESDAY,
+            Calendar.WEDNESDAY,
+            Calendar.THURSDAY,
+            Calendar.FRIDAY,
+            Calendar.SATURDAY,
+            Calendar.SUNDAY,
+        };
+
+        // Bitmask of all repeating days
+        private int mDays;
+
+        DaysOfWeek(int days) {
+            mDays = days;
+        }
+
+        public String toString(Context context, boolean showNever) {
+            StringBuilder ret = new StringBuilder();
+
+            // no days
+            if (mDays == 0) {
+                return showNever ?
+                        context.getText(R.string.never).toString() : "";
+            }
+
+            // every day
+            if (mDays == 0x7f) {
+                return context.getText(R.string.every_day).toString();
+            }
+
+            // count selected days
+            int dayCount = 0, days = mDays;
+            while (days > 0) {
+                if ((days & 1) == 1) dayCount++;
+                days >>= 1;
+            }
+
+            // short or long form?
+            DateFormatSymbols dfs = new DateFormatSymbols();
+            String[] dayList = (dayCount > 1) ?
+                    dfs.getShortWeekdays() :
+                    dfs.getWeekdays();
+
+            // selected days
+            for (int i = 0; i < 7; i++) {
+                if ((mDays & (1 << i)) != 0) {
+                    ret.append(dayList[DAY_MAP[i]]);
+                    dayCount -= 1;
+                    if (dayCount > 0) ret.append(
+                            context.getText(R.string.day_concat));
+                }
+            }
+            return ret.toString();
+        }
+
+        private boolean isSet(int day) {
+            return ((mDays & (1 << day)) > 0);
+        }
+
+        public void set(int day, boolean set) {
+            if (set) {
+                mDays |= (1 << day);
+            } else {
+                mDays &= ~(1 << day);
+            }
+        }
+
+        public void set(DaysOfWeek dow) {
+            mDays = dow.mDays;
+        }
+
+        public int getCoded() {
+            return mDays;
+        }
+
+        // Returns days of week encoded in an array of booleans.
+        public boolean[] getBooleanArray() {
+            boolean[] ret = new boolean[7];
+            for (int i = 0; i < 7; i++) {
+                ret[i] = isSet(i);
+            }
+            return ret;
+        }
+
+        public boolean isRepeatSet() {
+            return mDays != 0;
+        }
+
+        /**
+         * returns number of days from today until next alarm
+         * @param c must be set to today
+         */
+        public int getNextAlarm(Calendar c) {
+            if (mDays == 0) {
+                return -1;
+            }
+
+            int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;
+
+            int day = 0;
+            int dayCount = 0;
+            for (; dayCount < 7; dayCount++) {
+                day = (today + dayCount) % 7;
+                if (isSet(day)) {
+                    break;
+                }
+            }
+            return dayCount;
+        }
+    }
+}
index 2a05abc..3567d87 100644 (file)
 package com.android.alarmclock;
 
 import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.BroadcastReceiver;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -41,77 +47,37 @@ import java.util.Calendar;
 public class AlarmAlert extends Activity {
 
     private static final String DEFAULT_SNOOZE = "10";
-    private static final int UNKNOWN = 0;
-    private static final int SNOOZE = 1;
-    private static final int DISMISS = 2;
-    private static final int KILLED = 3;
-    private Button mSnoozeButton;
-    private int mState = UNKNOWN;
-
-    private AlarmKlaxon mKlaxon;
-    private int mAlarmId;
-    private String mLabel;
+    private Alarm mAlarm;
+
+    // Receives the ALARM_KILLED action from the AlarmKlaxon.
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
+            if (mAlarm.id == alarm.id) {
+                dismiss(true);
+            }
+        }
+    };
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        // Maintain a lock during the playback of the alarm. This lock may have
-        // already been acquired in AlarmReceiver. If the process was killed,
-        // the global wake lock is gone. Acquire again just to be sure.
-        AlarmAlertWakeLock.acquireCpuWakeLock(this);
-
-        /* FIXME Intentionally verbose: always log this until we've
-           fully debugged the app failing to start up */
-        Log.v("AlarmAlert.onCreate()");
-
-        Intent i = getIntent();
-        mAlarmId = i.getIntExtra(Alarms.ID, -1);
-
-        mKlaxon = new AlarmKlaxon();
-        mKlaxon.postPlay(this, mAlarmId);
-
-        /* allow next alarm to trigger while this activity is
-           active */
-        Alarms.disableSnoozeAlert(AlarmAlert.this);
-        Alarms.disableAlert(AlarmAlert.this, mAlarmId);
-        Alarms.setNextAlert(this);
-
-        mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() {
-            public void onKilled() {
-                if (Log.LOGV) Log.v("onKilled()");
-                updateSilencedText();
-
-                /* don't allow snooze */
-                mSnoozeButton.setEnabled(false);
-
-                // Dismiss the alarm but mark the state as killed so if the
-                // config changes, we show the silenced message and disable
-                // snooze.
-                dismiss();
-                mState = KILLED;
-            }
-        });
+        mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
 
         requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         updateLayout();
-    }
 
-    private void setTitleFromIntent(Intent i) {
-        mLabel = i.getStringExtra(Alarms.LABEL);
-        if (mLabel == null || mLabel.length() == 0) {
-            mLabel = getString(R.string.default_label);
-        }
-        TextView title = (TextView) findViewById(R.id.alertTitle);
-        title.setText(mLabel);
+        // Register to get the alarm killed intent.
+        registerReceiver(mReceiver, new IntentFilter(Alarms.ALARM_KILLED));
     }
 
-    private void updateSilencedText() {
-        TextView silenced = (TextView) findViewById(R.id.silencedText);
-        silenced.setText(getString(R.string.alarm_alert_alert_silenced,
-                    AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60));
-        silenced.setVisibility(View.VISIBLE);
+    private void setTitle() {
+        String label = mAlarm.getLabelOrDefault(this);
+        TextView title = (TextView) findViewById(R.id.alertTitle);
+        title.setText(label);
     }
 
     // This method is overwritten in AlarmAlertFullScreen in order to show a
@@ -141,42 +107,28 @@ public class AlarmAlert extends Activity {
 
         /* snooze behavior: pop a snooze confirmation view, kick alarm
            manager. */
-        mSnoozeButton = (Button) findViewById(R.id.snooze);
-        mSnoozeButton.requestFocus();
-        // If this was a configuration change, keep the silenced text if the
-        // alarm was killed.
-        if (mState == KILLED) {
-            updateSilencedText();
-            mSnoozeButton.setEnabled(false);
-        } else {
-            mSnoozeButton.setOnClickListener(new Button.OnClickListener() {
-                public void onClick(View v) {
-                    snooze();
-                    finish();
-                }
-            });
-        }
+        Button snooze = (Button) findViewById(R.id.snooze);
+        snooze.requestFocus();
+        snooze.setOnClickListener(new Button.OnClickListener() {
+            public void onClick(View v) {
+                snooze();
+            }
+        });
 
         /* dismiss button: close notification */
         findViewById(R.id.dismiss).setOnClickListener(
                 new Button.OnClickListener() {
                     public void onClick(View v) {
-                        dismiss();
-                        finish();
+                        dismiss(false);
                     }
                 });
 
-        /* Set the title from the passed in label */
-        setTitleFromIntent(getIntent());
+        /* Set the title from the passed in alarm */
+        setTitle();
     }
 
     // Attempt to snooze this alert.
     private void snooze() {
-        if (mState != UNKNOWN) {
-            return;
-        }
-        // If the next alarm is set for sooner than the snooze interval, don't
-        // snooze. Instead, toast the user that the snooze will not be set.
         final String snooze =
                 PreferenceManager.getDefaultSharedPreferences(this)
                 .getString("snooze_duration", DEFAULT_SNOOZE);
@@ -184,39 +136,58 @@ public class AlarmAlert extends Activity {
 
         final long snoozeTime = System.currentTimeMillis()
                 + (1000 * 60 * snoozeMinutes);
-        final long nextAlarm =
-                Alarms.calculateNextAlert(AlarmAlert.this).getAlert();
-        String displayTime = null;
-        if (nextAlarm < snoozeTime) {
-            final Calendar c = Calendar.getInstance();
-            c.setTimeInMillis(nextAlarm);
-            displayTime = getString(R.string.alarm_alert_snooze_not_set,
-                    Alarms.formatTime(AlarmAlert.this, c));
-            mState = DISMISS;
-        } else {
-            Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime,
-                    mLabel);
-            Alarms.setNextAlert(AlarmAlert.this);
-            displayTime = getString(R.string.alarm_alert_snooze_set,
-                    snoozeMinutes);
-            mState = SNOOZE;
-        }
+        Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarm.id, snoozeTime);
+
+        // Get the display time for the snooze and update the notification.
+        final Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(snoozeTime);
+
+        // Append (snoozed) to the label.
+        String label = mAlarm.getLabelOrDefault(this);
+        label = getString(R.string.alarm_notify_snooze_label, label);
+
+        // Notify the user that the alarm has been snoozed.
+        Intent cancelSnooze = new Intent(this, AlarmReceiver.class);
+        cancelSnooze.setAction(Alarms.CANCEL_SNOOZE);
+        cancelSnooze.putExtra(Alarms.ALARM_ID, mAlarm.id);
+        PendingIntent broadcast =
+                PendingIntent.getBroadcast(this, mAlarm.id, cancelSnooze, 0);
+        NotificationManager nm = getNotificationManager();
+        Notification n = new Notification(R.drawable.stat_notify_alarm,
+                label, 0);
+        n.setLatestEventInfo(this, label,
+                getString(R.string.alarm_notify_snooze_text,
+                    Alarms.formatTime(this, c)), broadcast);
+        n.deleteIntent = broadcast;
+        n.flags |= Notification.FLAG_AUTO_CANCEL;
+        nm.notify(mAlarm.id, n);
+
+        String displayTime = getString(R.string.alarm_alert_snooze_set,
+                snoozeMinutes);
         // Intentionally log the snooze time for debugging.
         Log.v(displayTime);
+
         // Display the snooze minutes in a toast.
         Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show();
-        mKlaxon.stop(this, mState == SNOOZE);
-        AlarmAlertWakeLock.release();
+        stopService(new Intent(Alarms.ALARM_ALERT_ACTION));
+        finish();
+    }
+
+    private NotificationManager getNotificationManager() {
+        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     }
 
     // Dismiss the alarm.
-    private void dismiss() {
-        if (mState != UNKNOWN) {
-            return;
+    private void dismiss(boolean killed) {
+        // The service told us that the alarm has been killed, do not modify
+        // the notification or stop the service.
+        if (!killed) {
+            // Cancel the notification and stop playing the alarm
+            NotificationManager nm = getNotificationManager();
+            nm.cancel(mAlarm.id);
+            stopService(new Intent(Alarms.ALARM_ALERT_ACTION));
         }
-        mState = DISMISS;
-        mKlaxon.stop(this, false);
-        AlarmAlertWakeLock.release();
+        finish();
     }
 
     /**
@@ -226,28 +197,18 @@ public class AlarmAlert extends Activity {
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
-        if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()");
-        mState = UNKNOWN;
-        mSnoozeButton.setEnabled(true);
-
-        mAlarmId = intent.getIntExtra(Alarms.ID, -1);
-        // Play the new alarm sound.
-        mKlaxon.postPlay(this, mAlarmId);
 
-        setTitleFromIntent(intent);
+        if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()");
 
-        /* unset silenced message */
-        TextView silenced = (TextView)findViewById(R.id.silencedText);
-        silenced.setVisibility(View.GONE);
+        mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
 
-        Alarms.setNextAlert(this);
-        setIntent(intent);
+        setTitle();
     }
 
     @Override
-    protected void onResume() {
+    protected void onStart() {
         super.onResume();
-        if (Log.LOGV) Log.v("AlarmAlert.onResume()");
+        if (Log.LOGV) Log.v("AlarmAlert.onStart()");
         // Acquire a separate lock for the screen to stay on. This is necessary
         // to avoid flashing the keyguard when the screen is locked.
         AlarmAlertWakeLock.acquireScreenWakeLock(this);
@@ -255,52 +216,35 @@ public class AlarmAlert extends Activity {
 
     @Override
     protected void onStop() {
-        super.onStop();
+        super.onPause();
         if (Log.LOGV) Log.v("AlarmAlert.onStop()");
-        // As a last resort, try to snooze if this activity is stopped.
-        snooze();
-        // We might have been killed by the KillerCallback so always release
-        // the lock.
-        AlarmAlertWakeLock.release();
+        AlarmAlertWakeLock.releaseScreenLock();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (Log.LOGV) Log.v("AlarmAlert.onDestroy()");
+        // No longer care about the alarm being killed.
+        unregisterReceiver(mReceiver);
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        // Do this on key down to handle a few of the system keys. Only handle
-        // the snooze and dismiss this alert if the state is unknown.
+        // Do this on key down to handle a few of the system keys.
         boolean up = event.getAction() == KeyEvent.ACTION_UP;
-        boolean dismiss = false;
         switch (event.getKeyCode()) {
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-            // Ignore ENDCALL because we do not receive the event if the screen
-            // is on. However, we do receive the key up for ENDCALL if the
-            // screen was off.
-            case KeyEvent.KEYCODE_ENDCALL:
-                break;
-            // Volume keys dismiss the alarm
+            // Volume keys and camera keys dismiss the alarm
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_CAMERA:
             case KeyEvent.KEYCODE_FOCUS:
-                dismiss = true;
-            // All other keys will snooze the alarm
-            default:
-                // Check for UNKNOWN here so that we intercept both key events
-                // and prevent the volume keys from triggering their default
-                // behavior.
-                if (mState == UNKNOWN && up) {
-                    if (dismiss) {
-                        dismiss();
-                    } else {
-                        snooze();
-                    }
-                    finish();
+                if (up) {
+                    dismiss(false);
                 }
                 return true;
+            default:
+                break;
         }
         return super.dispatchKeyEvent(event);
     }
index 0291df8..d6ab764 100644 (file)
@@ -60,12 +60,16 @@ class AlarmAlertWakeLock {
         sScreenWakeLock.acquire();
     }
 
-    static void release() {
-        Log.v("Releasing wake lock");
+    static void releaseCpuLock() {
+        Log.v("Releasing cpu wake lock");
         if (sCpuWakeLock != null) {
             sCpuWakeLock.release();
             sCpuWakeLock = null;
         }
+    }
+
+    static void releaseScreenLock() {
+        Log.v("Releasing screen lock");
         if (sScreenWakeLock != null) {
             sScreenWakeLock.release();
             sScreenWakeLock = null;
index d1dabb2..f84db84 100644 (file)
@@ -106,42 +106,36 @@ public class AlarmClock extends Activity implements OnItemClickListener {
         }
 
         public void bindView(View view, Context context, Cursor cursor) {
-            final int id = cursor.getInt(Alarms.AlarmColumns.ALARM_ID_INDEX);
-            final int hour = cursor.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX);
-            final int minutes = cursor.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX);
-            final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek(
-                    cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX));
-            final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1;
-            final String label =
-                    cursor.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX);
+            final Alarm alarm = new Alarm(cursor);
 
             CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton);
-            onButton.setChecked(enabled);
+            onButton.setChecked(alarm.enabled);
             onButton.setOnClickListener(new OnClickListener() {
                     public void onClick(View v) {
                         boolean isChecked = ((CheckBox) v).isChecked();
-                        Alarms.enableAlarm(AlarmClock.this, id, isChecked);
+                        Alarms.enableAlarm(AlarmClock.this, alarm.id,
+                            isChecked);
                         if (isChecked) {
-                            SetAlarm.popAlarmSetToast(
-                                    AlarmClock.this, hour, minutes, daysOfWeek);
+                            SetAlarm.popAlarmSetToast(AlarmClock.this,
+                                alarm.hour, alarm.minutes, alarm.daysOfWeek);
                         }
                     }
             });
 
-            DigitalClock digitalClock = (DigitalClock)view.findViewById(R.id.digitalClock);
-            if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour +
-                                ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock);
+            DigitalClock digitalClock =
+                    (DigitalClock) view.findViewById(R.id.digitalClock);
 
             // set the alarm text
             final Calendar c = Calendar.getInstance();
-            c.set(Calendar.HOUR_OF_DAY, hour);
-            c.set(Calendar.MINUTE, minutes);
+            c.set(Calendar.HOUR_OF_DAY, alarm.hour);
+            c.set(Calendar.MINUTE, alarm.minutes);
             digitalClock.updateTime(c);
 
             // Set the repeat text or leave it blank if it does not repeat.
-            TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek);
+            TextView daysOfWeekView =
+                    (TextView) digitalClock.findViewById(R.id.daysOfWeek);
             final String daysOfWeekStr =
-                    daysOfWeek.toString(AlarmClock.this, false);
+                    alarm.daysOfWeek.toString(AlarmClock.this, false);
             if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) {
                 daysOfWeekView.setText(daysOfWeekStr);
                 daysOfWeekView.setVisibility(View.VISIBLE);
@@ -152,8 +146,8 @@ public class AlarmClock extends Activity implements OnItemClickListener {
             // Display the label
             TextView labelView =
                     (TextView) digitalClock.findViewById(R.id.label);
-            if (label != null && label.length() != 0) {
-                labelView.setText(label);
+            if (alarm.label != null && alarm.label.length() != 0) {
+                labelView.setText(alarm.label);
                 labelView.setVisibility(View.VISIBLE);
             } else {
                 labelView.setVisibility(View.GONE);
@@ -161,10 +155,6 @@ public class AlarmClock extends Activity implements OnItemClickListener {
         }
     };
 
-    private boolean isAlarmEnabled(final Cursor c) {
-        return c.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1;
-    }
-
     @Override
     public boolean onContextItemSelected(final MenuItem item) {
         final AdapterContextMenuInfo info =
@@ -190,16 +180,11 @@ public class AlarmClock extends Activity implements OnItemClickListener {
             case R.id.enable_alarm:
                 final Cursor c = (Cursor) mAlarmsList.getAdapter()
                         .getItem(info.position);
-                boolean enabled = isAlarmEnabled(c);
-                Alarms.enableAlarm(this, id, !enabled);
-                if (!enabled) {
-                    final int hour =
-                            c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX);
-                    final int minutes =
-                            c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX);
-                    final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek(
-                            c.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX));
-                    SetAlarm.popAlarmSetToast(this, hour, minutes, daysOfWeek);
+                final Alarm alarm = new Alarm(c);
+                Alarms.enableAlarm(this, alarm.id, !alarm.enabled);
+                if (!alarm.enabled) {
+                    SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes,
+                            alarm.daysOfWeek);
                 }
                 return true;
 
@@ -320,15 +305,12 @@ public class AlarmClock extends Activity implements OnItemClickListener {
         final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
         final Cursor c =
                 (Cursor) mAlarmsList.getAdapter().getItem((int) info.position);
-        final int hour = c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX);
-        final int minutes = c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX);
-        final String label =
-                c.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX);
+        final Alarm alarm = new Alarm(c);
 
         // Construct the Calendar to compute the time.
         final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, hour);
-        cal.set(Calendar.MINUTE, minutes);
+        cal.set(Calendar.HOUR_OF_DAY, alarm.hour);
+        cal.set(Calendar.MINUTE, alarm.minutes);
         final String time = Alarms.formatTime(this, cal);
 
         // Inflate the custom view and set each TextView's text.
@@ -336,19 +318,19 @@ public class AlarmClock extends Activity implements OnItemClickListener {
         TextView textView = (TextView) v.findViewById(R.id.header_time);
         textView.setText(time);
         textView = (TextView) v.findViewById(R.id.header_label);
-        textView.setText(label);
+        textView.setText(alarm.label);
 
         // Set the custom view on the menu.
         menu.setHeaderView(v);
         // Change the text to "disable" if the alarm is already enabled.
-        if (isAlarmEnabled(c)) {
+        if (alarm.enabled) {
             menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm);
         }
     }
 
     public void onItemClick(AdapterView parent, View v, int pos, long id) {
         Intent intent = new Intent(this, SetAlarm.class);
-        intent.putExtra(Alarms.ID, (int) id);
+        intent.putExtra(Alarms.ALARM_ID, (int) id);
         startActivity(intent);
     }
 
@@ -378,7 +360,7 @@ public class AlarmClock extends Activity implements OnItemClickListener {
                     Log.v("In AlarmClock, new alarm id = " + newId);
                 }
                 Intent intent = new Intent(this, SetAlarm.class);
-                intent.putExtra(Alarms.ID, newId);
+                intent.putExtra(Alarms.ALARM_ID, newId);
                 startActivity(intent);
                 return true;
 
index 77549b0..8657e03 100644 (file)
@@ -36,7 +36,7 @@ public class AlarmInitReceiver extends BroadcastReceiver {
             return;
         }
         if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-            Alarms.disableSnoozeAlert(context);
+            Alarms.saveSnoozeAlert(context, -1, -1);
             Alarms.disableExpiredAlarms(context);
         }
         Alarms.setNextAlert(context);
index 4cf306b..b634860 100644 (file)
@@ -16,8 +16,9 @@
 
 package com.android.alarmclock;
 
-import android.content.ContentResolver;
+import android.app.Service;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.media.AudioManager;
@@ -27,6 +28,7 @@ import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.Vibrator;
 import android.telephony.TelephonyManager;
@@ -35,29 +37,21 @@ import android.telephony.TelephonyManager;
  * Manages alarms and vibe.  Singleton, so it can be initiated in
  * AlarmReceiver and shut down in the AlarmAlert activity
  */
-class AlarmKlaxon implements Alarms.AlarmSettings {
-
-    interface KillerCallback {
-        public void onKilled();
-    }
+public class AlarmKlaxon extends Service {
 
     /** Play alarm up to 10 minutes before silencing */
-    final static int ALARM_TIMEOUT_SECONDS = 10 * 60;
+    private static final int ALARM_TIMEOUT_SECONDS = 10 * 60;
 
     private static final long[] sVibratePattern = new long[] { 500, 500 };
 
-    private int mAlarmId;
-    private String mAlert;
-    private Alarms.DaysOfWeek mDaysOfWeek;
-    private boolean mVibrate;
     private boolean mPlaying = false;
     private Vibrator mVibrator;
     private MediaPlayer mMediaPlayer;
-    private KillerCallback mKillerCallback;
+    private Alarm mCurrentAlarm;
+    private long mStartTime;
 
     // Internal messages
     private static final int KILLER = 1000;
-    private static final int PLAY   = 1001;
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -65,67 +59,80 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
                     if (Log.LOGV) {
                         Log.v("*********** Alarm killer triggered ***********");
                     }
-                    if (mKillerCallback != null) {
-                        mKillerCallback.onKilled();
-                    }
-                    break;
-                case PLAY:
-                    play((Context) msg.obj, msg.arg1);
+                    sendKillBroadcast((Alarm) msg.obj);
+                    stopSelf();
                     break;
             }
         }
     };
 
-    AlarmKlaxon() {
+    @Override
+    public void onCreate() {
         mVibrator = new Vibrator();
+        AlarmAlertWakeLock.acquireCpuWakeLock(this);
     }
 
-    public void reportAlarm(
-            int idx, boolean enabled, int hour, int minutes,
-            Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message,
-            String alert) {
-        if (Log.LOGV) Log.v("AlarmKlaxon.reportAlarm: " + idx + " " + hour +
-                            " " + minutes + " dow " + daysOfWeek);
-        mAlert = alert;
-        mDaysOfWeek = daysOfWeek;
-        mVibrate = vibrate;
+    @Override
+    public void onDestroy() {
+        stop();
+        AlarmAlertWakeLock.releaseCpuLock();
     }
 
-    public void postPlay(final Context context, final int alarmId) {
-        mHandler.sendMessage(mHandler.obtainMessage(PLAY, alarmId, 0, context));
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
     }
 
-    // Volume suggested by media team for in-call alarms.
-    private static final float IN_CALL_VOLUME = 0.125f;
+    @Override
+    public void onStart(Intent intent, int startId) {
+        final Alarm alarm = intent.getParcelableExtra(
+                Alarms.ALARM_INTENT_EXTRA);
 
-    private void play(Context context, int alarmId) {
-        ContentResolver contentResolver = context.getContentResolver();
+        if (alarm == null) {
+            Log.v("AlarmKlaxon failed to parse the alarm from the intent");
+            return;
+        }
+
+        if (mCurrentAlarm != null) {
+            sendKillBroadcast(mCurrentAlarm);
+        }
 
-        if (mPlaying) stop(context, false);
+        play(alarm);
+        mCurrentAlarm = alarm;
+    }
+
+    private void sendKillBroadcast(Alarm alarm) {
+        long millis = System.currentTimeMillis() - mStartTime;
+        int minutes = (int) Math.round(millis / 60000.0);
+        Intent alarmKilled = new Intent(Alarms.ALARM_KILLED);
+        alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
+        alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes);
+        sendBroadcast(alarmKilled);
+    }
 
-        mAlarmId = alarmId;
+    // Volume suggested by media team for in-call alarms.
+    private static final float IN_CALL_VOLUME = 0.125f;
 
-        /* this will call reportAlarm() callback */
-        Alarms.getAlarm(contentResolver, this, mAlarmId);
+    private void play(Alarm alarm) {
+        // stop() checks to see if we are already playing.
+        stop();
 
-        if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert);
+        if (Log.LOGV) {
+            Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert);
+        }
 
-        // Fall back on the default alarm if the database does not have an
-        // alarm stored.
-        Uri alert = null;
-        boolean silent = false;
-        if (mAlert == null) {
-            alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
-            if (Log.LOGV) {
-                Log.v("Using default alarm: " + alert.toString());
+        if (!alarm.silent) {
+            Uri alert = alarm.alert;
+            // Fall back on the default alarm if the database does not have an
+            // alarm stored.
+            if (alert == null) {
+                alert = RingtoneManager.getDefaultUri(
+                        RingtoneManager.TYPE_ALARM);
+                if (Log.LOGV) {
+                    Log.v("Using default alarm: " + alert.toString());
+                }
             }
-        } else if (Alarms.ALARM_ALERT_SILENT.equals(mAlert)) {
-            silent = true;
-        } else {
-            alert = Uri.parse(mAlert);
-        }
 
-        if (!silent) {
             // TODO: Reuse mMediaPlayer instead of creating a new one and/or use
             // RingtoneManager.
             mMediaPlayer = new MediaPlayer();
@@ -141,17 +148,17 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
 
             try {
                 TelephonyManager tm =
-                        (TelephonyManager) context.getSystemService(
+                        (TelephonyManager) getSystemService(
                                 Context.TELEPHONY_SERVICE);
                 // Check if we are in a call. If we are, use the in-call alarm
                 // resource at a low volume to not disrupt the call.
                 if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
                     Log.v("Using the in-call alarm");
                     mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
-                    setDataSourceFromResource(context.getResources(),
-                            mMediaPlayer, R.raw.in_call_alarm);
+                    setDataSourceFromResource(getResources(), mMediaPlayer,
+                            R.raw.in_call_alarm);
                 } else {
-                    mMediaPlayer.setDataSource(context, alert);
+                    mMediaPlayer.setDataSource(this, alert);
                 }
                 startAlarm(mMediaPlayer);
             } catch (Exception ex) {
@@ -161,8 +168,7 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
                 try {
                     // Must reset the media player to clear the error state.
                     mMediaPlayer.reset();
-                    setDataSourceFromResource(context.getResources(),
-                            mMediaPlayer,
+                    setDataSourceFromResource(getResources(), mMediaPlayer,
                             com.android.internal.R.raw.fallbackring);
                     startAlarm(mMediaPlayer);
                 } catch (Exception ex2) {
@@ -173,14 +179,15 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
         }
 
         /* Start the vibrator after everything is ok with the media player */
-        if (mVibrate) {
+        if (alarm.vibrate) {
             mVibrator.vibrate(sVibratePattern, 0);
         } else {
             mVibrator.cancel();
         }
 
-        enableKiller();
+        enableKiller(alarm);
         mPlaying = true;
+        mStartTime = System.currentTimeMillis();
     }
 
     // Do the common stuff when starting the alarm.
@@ -207,8 +214,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
      * Stops alarm audio and disables alarm if it not snoozed and not
      * repeating
      */
-    public void stop(Context context, boolean snoozed) {
-        if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId);
+    public void stop() {
+        if (Log.LOGV) Log.v("AlarmKlaxon.stop()");
         if (mPlaying) {
             mPlaying = false;
 
@@ -221,32 +228,19 @@ class AlarmKlaxon implements Alarms.AlarmSettings {
 
             // Stop vibrator
             mVibrator.cancel();
-
-            /* disable alarm only if it is not set to repeat */
-            if (!snoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) {
-                Alarms.enableAlarm(context, mAlarmId, false);
-            }
         }
         disableKiller();
     }
 
     /**
-     * This callback called when alarm killer times out unattended
-     * alarm
-     */
-    public void setKillerCallback(KillerCallback killerCallback) {
-        mKillerCallback = killerCallback;
-    }
-
-    /**
      * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm
      * won't run all day.
      *
      * This just cancels the audio, but leaves the notification
      * popped, so the user will know that the alarm tripped.
      */
-    private void enableKiller() {
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER),
+    private void enableKiller(Alarm alarm) {
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm),
                 1000 * ALARM_TIMEOUT_SECONDS);
     }
 
index 74fdd2e..5849a38 100644 (file)
@@ -173,38 +173,38 @@ public class AlarmProvider extends ContentProvider {
         else
             values = new ContentValues();
 
-        if (!values.containsKey(Alarms.AlarmColumns.HOUR))
-            values.put(Alarms.AlarmColumns.HOUR, 0);
+        if (!values.containsKey(Alarm.Columns.HOUR))
+            values.put(Alarm.Columns.HOUR, 0);
 
-        if (!values.containsKey(Alarms.AlarmColumns.MINUTES))
-            values.put(Alarms.AlarmColumns.MINUTES, 0);
+        if (!values.containsKey(Alarm.Columns.MINUTES))
+            values.put(Alarm.Columns.MINUTES, 0);
 
-        if (!values.containsKey(Alarms.AlarmColumns.DAYS_OF_WEEK))
-            values.put(Alarms.AlarmColumns.DAYS_OF_WEEK, 0);
+        if (!values.containsKey(Alarm.Columns.DAYS_OF_WEEK))
+            values.put(Alarm.Columns.DAYS_OF_WEEK, 0);
 
-        if (!values.containsKey(Alarms.AlarmColumns.ALARM_TIME))
-            values.put(Alarms.AlarmColumns.ALARM_TIME, 0);
+        if (!values.containsKey(Alarm.Columns.ALARM_TIME))
+            values.put(Alarm.Columns.ALARM_TIME, 0);
 
-        if (!values.containsKey(Alarms.AlarmColumns.ENABLED))
-            values.put(Alarms.AlarmColumns.ENABLED, 0);
+        if (!values.containsKey(Alarm.Columns.ENABLED))
+            values.put(Alarm.Columns.ENABLED, 0);
 
-        if (!values.containsKey(Alarms.AlarmColumns.VIBRATE))
-            values.put(Alarms.AlarmColumns.VIBRATE, 1);
+        if (!values.containsKey(Alarm.Columns.VIBRATE))
+            values.put(Alarm.Columns.VIBRATE, 1);
 
-        if (!values.containsKey(Alarms.AlarmColumns.MESSAGE))
-            values.put(Alarms.AlarmColumns.MESSAGE, "");
+        if (!values.containsKey(Alarm.Columns.MESSAGE))
+            values.put(Alarm.Columns.MESSAGE, "");
 
-        if (!values.containsKey(Alarms.AlarmColumns.ALERT))
-            values.put(Alarms.AlarmColumns.ALERT, "");
+        if (!values.containsKey(Alarm.Columns.ALERT))
+            values.put(Alarm.Columns.ALERT, "");
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        long rowId = db.insert("alarms", Alarms.AlarmColumns.MESSAGE, values);
+        long rowId = db.insert("alarms", Alarm.Columns.MESSAGE, values);
         if (rowId < 0) {
             throw new SQLException("Failed to insert row into " + url);
         }
         if (Log.LOGV) Log.v("Added alarm rowId = " + rowId);
 
-        Uri newUrl = ContentUris.withAppendedId(Alarms.AlarmColumns.CONTENT_URI, rowId);
+        Uri newUrl = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, rowId);
         getContext().getContentResolver().notifyChange(newUrl, null);
         return newUrl;
     }
index eed76b6..7844283 100644 (file)
 package com.android.alarmclock;
 
 import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.BroadcastReceiver;
+import android.database.Cursor;
+import android.os.Parcel;
 
 /**
  * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert
@@ -33,28 +39,58 @@ public class AlarmReceiver extends BroadcastReceiver {
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        long now = System.currentTimeMillis();
-        int id = intent.getIntExtra(Alarms.ID, 0);
-        long setFor = intent.getLongExtra(Alarms.TIME, 0);
+        // Take care of the easy intents first.
+        if (Alarms.CLEAR_NOTIFICATION.equals(intent.getAction())) {
+            // If this is the "Clear All Notifications" intent, stop the alarm
+            // service and return.
+            context.stopService(new Intent(Alarms.ALARM_ALERT_ACTION));
+            return;
+        } else if (Alarms.ALARM_KILLED.equals(intent.getAction())) {
+            // The alarm has been killed, update the notification
+            updateNotification(context, (Alarm)
+                    intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA),
+                    intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1));
+            return;
+        } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) {
+            Alarms.saveSnoozeAlert(context, -1, -1);
+            return;
+        }
+
+        Alarm alarm = null;
+        // Grab the alarm from the intent. Since the remote AlarmManagerService
+        // fills in the Intent to add some extra data, it must unparcel the
+        // Alarm object. It throws a ClassNotFoundException when unparcelling.
+        // To avoid this, do the marshalling ourselves.
+        final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA);
+        if (data != null) {
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            alarm = Alarm.CREATOR.createFromParcel(in);
+        }
 
+        if (alarm == null) {
+            Log.v("AlarmReceiver failed to parse the alarm from the intent");
+            return;
+        }
+
+        long now = System.currentTimeMillis();
         /* FIXME Intentionally verbose: always log this until we've
            fully debugged the app failing to start up */
-        Log.v("AlarmReceiver.onReceive() id " + id + " setFor " + setFor +
-              " now " + now);
+        Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor "
+                + alarm.time + " now " + now);
 
-        if (now > setFor + STALE_WINDOW * 1000) {
-            if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id"
-                                + id + " setFor " + setFor + " now " + now);
+        if (now > alarm.time + STALE_WINDOW * 1000) {
+            if (Log.LOGV) {
+                Log.v("AlarmReceiver ignoring stale alarm intent id" + alarm.id
+                        + " setFor " + alarm.time + " now " + now);
+            }
             return;
         }
 
-        /* Wake the device and stay awake until the AlarmAlert intent is
-         * handled. */
-        AlarmAlertWakeLock.acquireCpuWakeLock(context);
-
         /* Close dialogs and window shade */
-        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        context.sendBroadcast(i);
+        Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        context.sendBroadcast(closeDialogs);
 
         // Decide which activity to start based on the state of the keyguard.
         Class c = AlarmAlert.class;
@@ -67,10 +103,94 @@ public class AlarmReceiver extends BroadcastReceiver {
 
         /* launch UI, explicitly stating that this is not due to user action
          * so that the current app's notification management is not disturbed */
-        Intent fireAlarm = new Intent(context, c);
-        fireAlarm.putExtra(Alarms.ID, id);
-        fireAlarm.putExtra(Alarms.LABEL, intent.getStringExtra(Alarms.LABEL));
-        fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-        context.startActivity(fireAlarm);
-   }
+        Intent alarmAlert = new Intent(context, c);
+        alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
+        alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+        context.startActivity(alarmAlert);
+
+        // Disable the snooze alert if this alarm is the snooze.
+        Alarms.disableSnoozeAlert(context, alarm.id);
+        // Disable this alarm if it does not repeat.
+        if (!alarm.daysOfWeek.isRepeatSet()) {
+            Alarms.enableAlarm(context, alarm.id, false);
+        }
+        // Enable the next alert if there is one.
+        Alarms.setNextAlert(context);
+
+        // Play the alarm alert and vibrate the device.
+        Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION);
+        playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
+        context.startService(playAlarm);
+
+        // Trigger a notification that, when clicked, will show the alarm alert
+        // dialog. No need to check for fullscreen since this will always be
+        // launched from a user action.
+        Intent notify = new Intent(context, AlarmAlert.class);
+        notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
+        PendingIntent pendingNotify = PendingIntent.getActivity(context,
+                alarm.id, notify, 0);
+
+        // Use the alarm's label or the default label as the ticker text and
+        // main text of the notification.
+        String label = alarm.getLabelOrDefault(context);
+        Notification n = new Notification(R.drawable.stat_notify_alarm,
+                label, alarm.time);
+        n.setLatestEventInfo(context, label,
+                context.getString(R.string.alarm_notify_text),
+                pendingNotify);
+        n.flags |= Notification.FLAG_SHOW_LIGHTS;
+        n.ledARGB = 0xFF00FF00;
+        n.ledOnMS = 500;
+        n.ledOffMS = 500;
+
+        // Set the deleteIntent for when the user clicks "Clear All
+        // Notifications"
+        Intent clearAll = new Intent(context, AlarmReceiver.class);
+        clearAll.setAction(Alarms.CLEAR_NOTIFICATION);
+        n.deleteIntent = PendingIntent.getBroadcast(context, 0, clearAll, 0);
+
+        // Send the notification using the alarm id to easily identify the
+        // correct notification.
+        NotificationManager nm = getNotificationManager(context);
+        nm.notify(alarm.id, n);
+
+        // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can
+        // pick it up.
+        AlarmAlertWakeLock.acquireCpuWakeLock(context);
+    }
+
+    private NotificationManager getNotificationManager(Context context) {
+        return (NotificationManager)
+                context.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    private void updateNotification(Context context, Alarm alarm, int timeout) {
+        NotificationManager nm = getNotificationManager(context);
+
+        // If the alarm is null, just cancel the notification.
+        if (alarm == null) {
+            if (Log.LOGV) {
+                Log.v("Cannot update notification for killer callback");
+            }
+            return;
+        }
+
+        // Launch SetAlarm when clicked.
+        Intent viewAlarm = new Intent(context, SetAlarm.class);
+        viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id);
+        PendingIntent intent =
+                PendingIntent.getActivity(context, alarm.id, viewAlarm, 0);
+
+        // Update the notification to indicate that the alert has been
+        // silenced.
+        String label = alarm.getLabelOrDefault(context);
+        Notification n = new Notification(R.drawable.stat_notify_alarm,
+                label, alarm.time);
+        n.setLatestEventInfo(context, label,
+                context.getString(R.string.alarm_alert_alert_silenced, timeout),
+                intent);
+        n.flags |= Notification.FLAG_AUTO_CANCEL;
+        nm.notify(alarm.id, n);
+    }
 }
index 40edf0b..1745b98 100644 (file)
@@ -17,8 +17,6 @@
 package com.android.alarmclock;
 
 import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -28,7 +26,7 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.net.Uri;
-import android.provider.BaseColumns;
+import android.os.Parcel;
 import android.provider.Settings;
 import android.text.format.DateFormat;
 
@@ -40,354 +38,111 @@ import java.text.DateFormatSymbols;
  */
 public class Alarms {
 
-    public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT";
-    public final static String ID = "alarm_id";
-    public final static String TIME = "alarm_time";
-    public final static String LABEL = "alarm_label";
-    public final static String ALARM_ALERT_SILENT = "silent";
+    // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It
+    // is a public action used in the manifest for receiving Alarm broadcasts
+    // from the alarm manager.
+    public static final String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT";
 
-    final static String PREF_SNOOZE_ID = "snooze_id";
-    final static String PREF_SNOOZE_TIME = "snooze_time";
-    final static String PREF_SNOOZE_LABEL = "snooze_label";
-
-    private final static String DM12 = "E h:mm aa";
-    private final static String DM24 = "E k:mm";
-
-    private final static String M12 = "h:mm aa";
-    private final static String M24 = "k:mm";
-
-    /**
-     * Mapping from days in this application (where Monday is 0) to
-     * days in DateFormatSymbols (where Monday is 2).
-     */
-    private static int[] DAY_MAP = new int[] {
-        Calendar.MONDAY,
-        Calendar.TUESDAY,
-        Calendar.WEDNESDAY,
-        Calendar.THURSDAY,
-        Calendar.FRIDAY,
-        Calendar.SATURDAY,
-        Calendar.SUNDAY,
-    };
-
-    static class DaysOfWeek {
-
-        int mDays;
-
-        /**
-         * Days of week coded as single int, convenient for DB
-         * storage:
-         *
-         * 0x00:  no day
-         * 0x01:  Monday
-         * 0x02:  Tuesday
-         * 0x04:  Wednesday
-         * 0x08:  Thursday
-         * 0x10:  Friday
-         * 0x20:  Saturday
-         * 0x40:  Sunday
-         */
-        DaysOfWeek() {
-            this(0);
-        }
-
-        DaysOfWeek(int days) {
-            mDays = days;
-        }
-
-        public String toString(Context context, boolean showNever) {
-            StringBuilder ret = new StringBuilder();
-
-            /* no days */
-            if (mDays == 0) return showNever ? context.getText(
-                    R.string.never).toString() : "";
-
-            /* every day */
-            if (mDays == 0x7f) {
-                return context.getText(R.string.every_day).toString();
-            }
-
-            /* count selected days */
-            int dayCount = 0, days = mDays;
-            while (days > 0) {
-                if ((days & 1) == 1) dayCount++;
-                days >>= 1;
-            }
-
-            /* short or long form? */
-            DateFormatSymbols dfs = new DateFormatSymbols();
-            String[] dayList = (dayCount > 1) ?
-                                    dfs.getShortWeekdays() :
-                                    dfs.getWeekdays();
-
-            /* selected days */
-            for (int i = 0; i < 7; i++) {
-                if ((mDays & (1 << i)) != 0) {
-                    ret.append(dayList[DAY_MAP[i]]);
-                    dayCount -= 1;
-                    if (dayCount > 0) ret.append(
-                            context.getText(R.string.day_concat));
-                }
-            }
-            return ret.toString();
-        }
-
-        /**
-         * @param day Mon=0 ... Sun=6
-         * @return true if given day is set
-         */
-        public boolean isSet(int day) {
-            return ((mDays & (1 << day)) > 0);
-        }
-
-        public void set(int day, boolean set) {
-            if (set) {
-                mDays |= (1 << day);
-            } else {
-                mDays &= ~(1 << day);
-            }
-        }
+    // This is a private action used when the user clears all notifications.
+    public static final String CLEAR_NOTIFICATION = "clear_notification";
 
-        public void set(DaysOfWeek dow) {
-            mDays = dow.mDays;
-        }
+    // This is a private action used by the AlarmKlaxon to update the UI to
+    // show the alarm has been killed.
+    public static final String ALARM_KILLED = "alarm_killed";
 
-        public int getCoded() {
-            return mDays;
-        }
+    // Extra in the ALARM_KILLED intent to indicate to the user how long the
+    // alarm played before being killed.
+    public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout";
 
-        public boolean equals(DaysOfWeek dow) {
-            return mDays == dow.mDays;
-        }
+    // This string is used to indicate a silent alarm in the db.
+    public static final String ALARM_ALERT_SILENT = "silent";
 
-        // Returns days of week encoded in an array of booleans.
-        public boolean[] getBooleanArray() {
-            boolean[] ret = new boolean[7];
-            for (int i = 0; i < 7; i++) {
-                ret[i] = isSet(i);
-            }
-            return ret;
-        }
+    // This intent is sent from the notification when the user cancels the
+    // snooze alert.
+    public static final String CANCEL_SNOOZE = "cancel_snooze";
 
-        public void setCoded(int days) {
-            mDays = days;
-        }
+    // This string is used when passing an Alarm object through an intent.
+    public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm";
 
-        /**
-         * @return true if alarm is set to repeat
-         */
-        public boolean isRepeatSet() {
-            return mDays != 0;
-        }
+    // This extra is the raw Alarm object data. It is used in the
+    // AlarmManagerService to avoid a ClassNotFoundException when filling in
+    // the Intent extras.
+    public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw";
 
-        /**
-         * @return true if alarm is set to repeat every day
-         */
-        public boolean isEveryDaySet() {
-            return mDays == 0x7f;
-        }
+    // This string is used to identify the alarm id passed to SetAlarm from the
+    // list of alarms.
+    public static final String ALARM_ID = "alarm_id";
 
+    final static String PREF_SNOOZE_ID = "snooze_id";
+    final static String PREF_SNOOZE_TIME = "snooze_time";
 
-        /**
-         * returns number of days from today until next alarm
-         * @param c must be set to today
-         */
-        public int getNextAlarm(Calendar c) {
-            if (mDays == 0) return -1;
-            int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;
-
-            int day, dayCount;
-            for (dayCount = 0; dayCount < 7; dayCount++) {
-                day = (today + dayCount) % 7;
-                if ((mDays & (1 << day)) > 0) {
-                    break;
-                }
-            }
-            return dayCount;
-        }
-    }
-
-    public static class AlarmColumns implements BaseColumns {
-
-        /**
-         * The content:// style URL for this table
-         */
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://com.android.alarmclock/alarm");
-
-        public static final String _ID = "_id";
-
-        /**
-         * The default sort order for this table
-         */
-        public static final String DEFAULT_SORT_ORDER = "_id ASC";
-
-        /**
-         * Hour in 24-hour localtime 0 - 23.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String HOUR = "hour";
-
-        /**
-         * Minutes in localtime 0 - 59
-         * <P>Type: INTEGER</P>
-         */
-        public static final String MINUTES = "minutes";
-
-        /**
-         * Days of week coded as integer
-         * <P>Type: INTEGER</P>
-         */
-        public static final String DAYS_OF_WEEK = "daysofweek";
-
-        /**
-         * Alarm time in UTC milliseconds from the epoch.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String ALARM_TIME = "alarmtime";
-
-        /**
-         * True if alarm is active
-         * <P>Type: BOOLEAN</P>
-         */
-        public static final String ENABLED = "enabled";
-
-        /**
-         * True if alarm should vibrate
-         * <P>Type: BOOLEAN</P>
-         */
-        public static final String VIBRATE = "vibrate";
-
-        /**
-         * Message to show when alarm triggers
-         * Note: not currently used
-         * <P>Type: STRING</P>
-         */
-        public static final String MESSAGE = "message";
-
-        /**
-         * Audio alert to play when alarm triggers
-         * <P>Type: STRING</P>
-         */
-        public static final String ALERT = "alert";
-
-        static final String[] ALARM_QUERY_COLUMNS = {
-            _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME,
-            ENABLED, VIBRATE, MESSAGE, ALERT};
-
-        /**
-         * These save calls to cursor.getColumnIndexOrThrow()
-         * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
-         */
-        public static final int ALARM_ID_INDEX = 0;
-        public static final int ALARM_HOUR_INDEX = 1;
-        public static final int ALARM_MINUTES_INDEX = 2;
-        public static final int ALARM_DAYS_OF_WEEK_INDEX = 3;
-        public static final int ALARM_TIME_INDEX = 4;
-        public static final int ALARM_ENABLED_INDEX = 5;
-        public static final int ALARM_VIBRATE_INDEX = 6;
-        public static final int ALARM_MESSAGE_INDEX = 7;
-        public static final int ALARM_ALERT_INDEX = 8;
-    }
+    private final static String DM12 = "E h:mm aa";
+    private final static String DM24 = "E k:mm";
 
-    /**
-     * getAlarm and getAlarms call this interface to report alarms in
-     * the database
-     */
-    static interface AlarmSettings {
-        void reportAlarm(
-                int idx, boolean enabled, int hour, int minutes,
-                DaysOfWeek daysOfWeek, boolean vibrate, String message,
-                String alert);
-    }
+    private final static String M12 = "h:mm aa";
+    private final static String M24 = "k:mm";
 
     /**
      * Creates a new Alarm.
      */
-    public synchronized static Uri addAlarm(ContentResolver contentResolver) {
+    public static Uri addAlarm(ContentResolver contentResolver) {
         ContentValues values = new ContentValues();
-        values.put(Alarms.AlarmColumns.HOUR, 8);
-        return contentResolver.insert(AlarmColumns.CONTENT_URI, values);
+        values.put(Alarm.Columns.HOUR, 8);
+        return contentResolver.insert(Alarm.Columns.CONTENT_URI, values);
     }
 
     /**
      * Removes an existing Alarm.  If this alarm is snoozing, disables
      * snooze.  Sets next alert.
      */
-    public synchronized static void deleteAlarm(
+    public static void deleteAlarm(
             Context context, int alarmId) {
 
         ContentResolver contentResolver = context.getContentResolver();
         /* If alarm is snoozing, lose it */
-        int snoozeId = getSnoozeAlarmId(context);
-        if (snoozeId == alarmId) disableSnoozeAlert(context);
+        disableSnoozeAlert(context, alarmId);
 
-        Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId);
-        deleteAlarm(contentResolver, uri);
+        Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId);
+        contentResolver.delete(uri, "", null);
 
         setNextAlert(context);
     }
 
-    private synchronized static void deleteAlarm(
-            ContentResolver contentResolver, Uri uri) {
-        contentResolver.delete(uri, "", null);
-    }
-
     /**
      * Queries all alarms
      * @return cursor over all alarms
      */
-    public synchronized static Cursor getAlarmsCursor(
-            ContentResolver contentResolver) {
+    public static Cursor getAlarmsCursor(ContentResolver contentResolver) {
         return contentResolver.query(
-                AlarmColumns.CONTENT_URI, AlarmColumns.ALARM_QUERY_COLUMNS,
-                null, null, AlarmColumns.DEFAULT_SORT_ORDER);
+                Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS,
+                null, null, Alarm.Columns.DEFAULT_SORT_ORDER);
     }
 
-    /**
-     * Calls the AlarmSettings.reportAlarm interface on all alarms found in db.
-     */
-    public synchronized static void getAlarms(
-            ContentResolver contentResolver, AlarmSettings alarmSettings) {
-        Cursor cursor = getAlarmsCursor(contentResolver);
-        getAlarms(alarmSettings, cursor);
-        cursor.close();
-    }
-
-    private synchronized static void getAlarms(
-            AlarmSettings alarmSettings, Cursor cur) {
-        if (cur.moveToFirst()) {
-            do {
-                // Get the field values
-                int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX);
-                int hour = cur.getInt(AlarmColumns.ALARM_HOUR_INDEX);
-                int minutes = cur.getInt(AlarmColumns.ALARM_MINUTES_INDEX);
-                int daysOfWeek = cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX);
-                boolean enabled = cur.getInt(AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false;
-                boolean vibrate = cur.getInt(AlarmColumns.ALARM_VIBRATE_INDEX) == 1 ? true : false;
-                String message = cur.getString(AlarmColumns.ALARM_MESSAGE_INDEX);
-                String alert = cur.getString(AlarmColumns.ALARM_ALERT_INDEX);
-                alarmSettings.reportAlarm(
-                        id, enabled, hour, minutes, new DaysOfWeek(daysOfWeek),
-                        vibrate, message, alert);
-            } while (cur.moveToNext());
-        }
+    // Private method to get a more limited set of alarms from the database.
+    private static Cursor getFilteredAlarmsCursor(
+            ContentResolver contentResolver) {
+        return contentResolver.query(Alarm.Columns.CONTENT_URI,
+                Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED,
+                null, null);
     }
 
     /**
-     * Calls the AlarmSettings.reportAlarm interface on alarm with given
-     * alarmId
+     * Return an Alarm object representing the alarm id in the database.
+     * Returns null if no alarm exists.
      */
-    public synchronized static void getAlarm(
-            ContentResolver contentResolver, AlarmSettings alarmSetting,
-            int alarmId) {
+    public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) {
         Cursor cursor = contentResolver.query(
-                ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId),
-                AlarmColumns.ALARM_QUERY_COLUMNS,
-                null, null, AlarmColumns.DEFAULT_SORT_ORDER);
-
-        getAlarms(alarmSetting, cursor);
-        cursor.close();
+                ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId),
+                Alarm.Columns.ALARM_QUERY_COLUMNS,
+                null, null, null);
+        Alarm alarm = null;
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                alarm = new Alarm(cursor);
+            }
+            cursor.close();
+        }
+        return alarm;
     }
 
 
@@ -405,32 +160,35 @@ public class Alarms {
      * @param message        corresponds to the MESSAGE column
      * @param alert          corresponds to the ALERT column
      */
-    public synchronized static void setAlarm(
+    public static void setAlarm(
             Context context, int id, boolean enabled, int hour, int minutes,
-            DaysOfWeek daysOfWeek, boolean vibrate, String message,
+            Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message,
             String alert) {
 
         ContentValues values = new ContentValues(8);
         ContentResolver resolver = context.getContentResolver();
-        long time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
+        // Set the alarm_time value if this alarm does not repeat. This will be
+        // used later to disable expired alarms.
+        long time = 0;
+        if (!daysOfWeek.isRepeatSet()) {
+            time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
+        }
 
         if (Log.LOGV) Log.v(
                 "**  setAlarm * idx " + id + " hour " + hour + " minutes " +
                 minutes + " enabled " + enabled + " time " + time);
 
-        values.put(AlarmColumns.ENABLED, enabled ? 1 : 0);
-        values.put(AlarmColumns.HOUR, hour);
-        values.put(AlarmColumns.MINUTES, minutes);
-        values.put(AlarmColumns.ALARM_TIME, time);
-        values.put(AlarmColumns.DAYS_OF_WEEK, daysOfWeek.getCoded());
-        values.put(AlarmColumns.VIBRATE, vibrate);
-        values.put(AlarmColumns.MESSAGE, message);
-        values.put(AlarmColumns.ALERT, alert);
-        resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id),
+        values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
+        values.put(Alarm.Columns.HOUR, hour);
+        values.put(Alarm.Columns.MINUTES, minutes);
+        values.put(Alarm.Columns.ALARM_TIME, time);
+        values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded());
+        values.put(Alarm.Columns.VIBRATE, vibrate);
+        values.put(Alarm.Columns.MESSAGE, message);
+        values.put(Alarm.Columns.ALERT, alert);
+        resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id),
                         values, null, null);
 
-        int aid = disableSnoozeAlert(context);
-        if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false);
         setNextAlert(context);
     }
 
@@ -441,103 +199,63 @@ public class Alarms {
      * @param enabled        corresponds to the ENABLED column
      */
 
-    public synchronized static void enableAlarm(
+    public static void enableAlarm(
             final Context context, final int id, boolean enabled) {
-        int aid = disableSnoozeAlert(context);
-        if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false);
         enableAlarmInternal(context, id, enabled);
         setNextAlert(context);
     }
 
-    private synchronized static void enableAlarmInternal(
-            final Context context, final int id, boolean enabled) {
-        ContentResolver resolver = context.getContentResolver();
+    private static void enableAlarmInternal(final Context context,
+            final int id, boolean enabled) {
+        enableAlarmInternal(context, getAlarm(context.getContentResolver(), id),
+                enabled);
+    }
 
-        class EnableAlarm implements AlarmSettings {
-            public int mHour;
-            public int mMinutes;
-            public DaysOfWeek mDaysOfWeek;
-
-            public void reportAlarm(
-                    int idx, boolean enabled, int hour, int minutes,
-                    DaysOfWeek daysOfWeek, boolean vibrate, String message,
-                    String alert) {
-                mHour = hour;
-                mMinutes = minutes;
-                mDaysOfWeek = daysOfWeek;
-            }
-        }
+    private static void enableAlarmInternal(final Context context,
+            final Alarm alarm, boolean enabled) {
+        ContentResolver resolver = context.getContentResolver();
 
         ContentValues values = new ContentValues(2);
-        values.put(AlarmColumns.ENABLED, enabled ? 1 : 0);
+        values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
 
-        /* If we are enabling the alarm, load hour/minutes/daysOfWeek
-           from db, so we can calculate alarm time */
+        // If we are enabling the alarm, calculate alarm time since the time
+        // value in Alarm may be old.
         if (enabled) {
-            EnableAlarm enableAlarm = new EnableAlarm();
-            getAlarm(resolver, enableAlarm, id);
-            if (enableAlarm.mDaysOfWeek == null) {
-                /* Under monkey, sometimes reportAlarm is never
-                   called */
-                Log.e("** enableAlarmInternal failed " + id + " h " +
-                      enableAlarm.mHour + " m " + enableAlarm.mMinutes);
-                return;
+            long time = 0;
+            if (!alarm.daysOfWeek.isRepeatSet()) {
+                time = calculateAlarm(alarm.hour, alarm.minutes,
+                        alarm.daysOfWeek).getTimeInMillis();
             }
-
-            long time = calculateAlarm(enableAlarm.mHour, enableAlarm.mMinutes,
-                                       enableAlarm.mDaysOfWeek).getTimeInMillis();
-            values.put(AlarmColumns.ALARM_TIME, time);
+            values.put(Alarm.Columns.ALARM_TIME, time);
         }
 
-        resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id),
-                        values, null, null);
+        resolver.update(ContentUris.withAppendedId(
+                Alarm.Columns.CONTENT_URI, alarm.id), values, null, null);
     }
 
-
-    /**
-     * Calculates next scheduled alert
-     */
-    static class AlarmCalculator implements AlarmSettings {
-        private long mMinAlert = Long.MAX_VALUE;
-        private int mMinIdx = -1;
-        private String mLabel;
-
-        /**
-         * returns next scheduled alert, MAX_VALUE if none
-         */
-        public long getAlert() {
-            return mMinAlert;
-        }
-        public int getIndex() {
-            return mMinIdx;
-        }
-        public String getLabel() {
-            return mLabel;
-        }
-
-        public void reportAlarm(
-                int idx, boolean enabled, int hour, int minutes,
-                DaysOfWeek daysOfWeek, boolean vibrate, String message,
-                String alert) {
-            if (enabled) {
-                long atTime = calculateAlarm(hour, minutes,
-                                             daysOfWeek).getTimeInMillis();
-                /* Log.i("**  SET ALERT* idx " + idx + " hour " + hour + " minutes " +
-                   minutes + " enabled " + enabled + " calc " + atTime); */
-                if (atTime < mMinAlert) {
-                    mMinIdx = idx;
-                    mMinAlert = atTime;
-                    mLabel = message;
-                }
+    public static Alarm calculateNextAlert(final Context context) {
+        Alarm alarm = null;
+        long minTime = Long.MAX_VALUE;
+        Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver());
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                do {
+                    Alarm a = new Alarm(cursor);
+                    // A time of 0 indicates this is a repeating alarm, so
+                    // calculate the time to get the next alert.
+                    if (a.time == 0) {
+                        a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek)
+                                .getTimeInMillis();
+                    }
+                    if (a.time < minTime) {
+                        minTime = a.time;
+                        alarm = a;
+                    }
+                } while (cursor.moveToNext());
             }
+            cursor.close();
         }
-    }
-
-    static AlarmCalculator calculateNextAlert(final Context context) {
-        ContentResolver resolver = context.getContentResolver();
-        AlarmCalculator alarmCalc = new AlarmCalculator();
-        getAlarms(resolver, alarmCalc);
-        return alarmCalc;
+        return alarm;
     }
 
     /**
@@ -545,54 +263,39 @@ public class Alarms {
      * boot.
      */
     public static void disableExpiredAlarms(final Context context) {
-        Cursor cur = getAlarmsCursor(context.getContentResolver());
+        Cursor cur = getFilteredAlarmsCursor(context.getContentResolver());
         long now = System.currentTimeMillis();
 
         if (cur.moveToFirst()) {
             do {
-                // Get the field values
-                int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX);
-                boolean enabled = cur.getInt(
-                        AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false;
-                DaysOfWeek daysOfWeek = new DaysOfWeek(
-                        cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX));
-                long time = cur.getLong(AlarmColumns.ALARM_TIME_INDEX);
-
-                if (enabled && !daysOfWeek.isRepeatSet() && time < now) {
-                    if (Log.LOGV) Log.v(
-                            "** DISABLE " + id + " now " + now +" set " + time);
-                    enableAlarmInternal(context, id, false);
+                Alarm alarm = new Alarm(cur);
+                // A time of 0 means this alarm repeats. If the time is
+                // non-zero, check if the time is before now.
+                if (alarm.time != 0 && alarm.time < now) {
+                    if (Log.LOGV) {
+                        Log.v("** DISABLE " + alarm.id + " now " + now +" set "
+                                + alarm.time);
+                    }
+                    enableAlarmInternal(context, alarm, false);
                 }
             } while (cur.moveToNext());
         }
         cur.close();
     }
 
-    private static NotificationManager getNotificationManager(
-            final Context context) {
-        return (NotificationManager) context.getSystemService(
-                context.NOTIFICATION_SERVICE);
-    }
-
     /**
      * Called at system startup, on time/timezone change, and whenever
      * the user changes alarm settings.  Activates snooze if set,
      * otherwise loads all alarms, activates next alert.
      */
     public static void setNextAlert(final Context context) {
-        int snoozeId = getSnoozeAlarmId(context);
-        if (snoozeId == -1) {
-            AlarmCalculator ac = calculateNextAlert(context);
-            int id = ac.getIndex();
-            long atTime = ac.getAlert();
-
-            if (atTime < Long.MAX_VALUE) {
-                enableAlert(context, id, ac.getLabel(), atTime);
+        if (!enableSnoozeAlert(context)) {
+            Alarm alarm = calculateNextAlert(context);
+            if (alarm != null) {
+                enableAlert(context, alarm, alarm.time);
             } else {
-                disableAlert(context, id);
+                disableAlert(context);
             }
-        } else {
-            enableSnoozeAlert(context);
         }
     }
 
@@ -600,37 +303,38 @@ public class Alarms {
      * Sets alert in AlarmManger and StatusBar.  This is what will
      * actually launch the alert when the alarm triggers.
      *
-     * Note: In general, apps should call setNextAlert() instead of
-     * this method.  setAlert() is only used outside this class when
-     * the alert is not to be driven by the state of the db.  "Snooze"
-     * uses this API, as we do not want to alter the alarm in the db
-     * with each snooze.
-     *
-     * @param id Alarm ID.
+     * @param alarm Alarm.
      * @param atTimeInMillis milliseconds since epoch
      */
-    static void enableAlert(Context context, int id, String label,
-           long atTimeInMillis) {
+    private static void enableAlert(Context context, final Alarm alarm,
+            final long atTimeInMillis) {
         AlarmManager am = (AlarmManager)
                 context.getSystemService(Context.ALARM_SERVICE);
 
-        Intent intent = new Intent(ALARM_ALERT_ACTION);
-        if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis);
-        intent.putExtra(ID, id);
-        if (label != null) {
-            intent.putExtra(LABEL, label);
+        if (Log.LOGV) {
+            Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis);
         }
-        intent.putExtra(TIME, atTimeInMillis);
+
+        Intent intent = new Intent(ALARM_ALERT_ACTION);
+
+        // XXX: This is a slight hack to avoid an exception in the remote
+        // AlarmManagerService process. The AlarmManager adds extra data to
+        // this Intent which causes it to inflate. Since the remote process
+        // does not know about the Alarm class, it throws a
+        // ClassNotFoundException.
+        //
+        // To avoid this, we marshall the data ourselves and then parcel a plain
+        // byte[] array. The AlarmReceiver class knows to build the Alarm
+        // object from the byte[] array.
+        Parcel out = Parcel.obtain();
+        alarm.writeToParcel(out, 0);
+        out.setDataPosition(0);
+        intent.putExtra(ALARM_RAW_DATA, out.marshall());
+
         PendingIntent sender = PendingIntent.getBroadcast(
                 context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
 
-        if (true) {
-            am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
-        } else {
-            // a five-second alarm, for testing
-            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000,
-                   sender);
-        }
+        am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
 
         setStatusBarIcon(context, true);
 
@@ -645,48 +349,49 @@ public class Alarms {
      *
      * @param id Alarm ID.
      */
-    static void disableAlert(Context context, int id) {
+    static void disableAlert(Context context) {
         AlarmManager am = (AlarmManager)
                 context.getSystemService(Context.ALARM_SERVICE);
-        Intent intent = new Intent(ALARM_ALERT_ACTION);
-        intent.putExtra(ID, id);
         PendingIntent sender = PendingIntent.getBroadcast(
-                context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                context, 0, new Intent(ALARM_ALERT_ACTION),
+                PendingIntent.FLAG_CANCEL_CURRENT);
         am.cancel(sender);
         setStatusBarIcon(context, false);
         saveNextAlarm(context, "");
     }
 
-    static void saveSnoozeAlert(final Context context, int id,
-                                long atTimeInMillis, String label) {
+    static void saveSnoozeAlert(final Context context, final int id,
+            final long time) {
         SharedPreferences prefs = context.getSharedPreferences(
                 AlarmClock.PREFERENCES, 0);
         SharedPreferences.Editor ed = prefs.edit();
-        ed.putInt(PREF_SNOOZE_ID, id);
-        ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis);
-        if (label != null) {
-            ed.putString(PREF_SNOOZE_LABEL, label);
+        if (id == -1) {
+            ed.clear();
+        } else {
+            ed.putInt(PREF_SNOOZE_ID, id);
+            ed.putLong(PREF_SNOOZE_TIME, time);
         }
         ed.commit();
+        // Set the next alert after updating the snooze.
+        setNextAlert(context);
     }
 
     /**
-     * @return ID of alarm disabled, if disabled, -1 otherwise
-     */
-    static int disableSnoozeAlert(final Context context) {
-        int id = getSnoozeAlarmId(context);
-        if (id == -1) return -1;
-        saveSnoozeAlert(context, -1, 0, null);
-        return id;
-    }
-
-    /**
-     * @return alarm ID of snoozing alarm, -1 if snooze unset
+     * Disable the snooze alert if the given id matches the snooze id.
      */
-    private static int getSnoozeAlarmId(final Context context) {
+    static void disableSnoozeAlert(final Context context, final int id) {
         SharedPreferences prefs = context.getSharedPreferences(
                 AlarmClock.PREFERENCES, 0);
-        return prefs.getInt(PREF_SNOOZE_ID, -1);
+        int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1);
+        if (snoozeId == -1) {
+            // No snooze set, do nothing.
+            return;
+        } else if (snoozeId == id) {
+            // This is the same id so clear the shared prefs.
+            SharedPreferences.Editor ed = prefs.edit();
+            ed.clear();
+            ed.commit();
+        }
     }
 
     /**
@@ -698,16 +403,22 @@ public class Alarms {
                 AlarmClock.PREFERENCES, 0);
 
         int id = prefs.getInt(PREF_SNOOZE_ID, -1);
-        if (id == -1) return false;
-        long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1);
-        if (id == -1) return false;
-        // Try to get the label from the snooze preference.
-        String label = prefs.getString(PREF_SNOOZE_LABEL, null);
-        enableAlert(context, id, label, atTimeInMillis);
+        if (id == -1) {
+            return false;
+        }
+        long time = prefs.getLong(PREF_SNOOZE_TIME, -1);
+
+        // Get the alarm from the db.
+        final Alarm alarm = getAlarm(context.getContentResolver(), id);
+        // The time in the database is either 0 (repeating) or a specific time
+        // for a non-repeating alarm. Update this value so the AlarmReceiver
+        // has the right time to compare.
+        alarm.time = time;
+
+        enableAlert(context, alarm, time);
         return true;
     }
 
-
     /**
      * Tells the StatusBar whether the alarm is enabled or disabled
      */
@@ -724,7 +435,7 @@ public class Alarms {
      * @param minute 0-59
      * @param daysOfWeek 0-59
      */
-    static Calendar calculateAlarm(int hour, int minute, DaysOfWeek daysOfWeek) {
+    static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) {
 
         // start with now
         Calendar c = Calendar.getInstance();
@@ -752,7 +463,7 @@ public class Alarms {
     }
 
     static String formatTime(final Context context, int hour, int minute,
-                             DaysOfWeek daysOfWeek) {
+                             Alarm.DaysOfWeek daysOfWeek) {
         Calendar c = calculateAlarm(hour, minute, daysOfWeek);
         return formatTime(context, c);
     }
index 7111ec6..6af023b 100644 (file)
@@ -28,10 +28,10 @@ import java.util.Calendar;
 public class RepeatPreference extends ListPreference {
 
     // Initial value that can be set with the values saved in the database.
-    private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek();
+    private Alarm.DaysOfWeek mDaysOfWeek = new Alarm.DaysOfWeek(0);
     // New value that will be set if a positive result comes back from the
     // dialog.
-    private Alarms.DaysOfWeek mNewDaysOfWeek = new Alarms.DaysOfWeek();
+    private Alarm.DaysOfWeek mNewDaysOfWeek = new Alarm.DaysOfWeek(0);
 
     public RepeatPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -73,13 +73,13 @@ public class RepeatPreference extends ListPreference {
                 });
     }
 
-    public void setDaysOfWeek(Alarms.DaysOfWeek dow) {
+    public void setDaysOfWeek(Alarm.DaysOfWeek dow) {
         mDaysOfWeek.set(dow);
         mNewDaysOfWeek.set(dow);
         setSummary(dow.toString(getContext(), true));
     }
 
-    public Alarms.DaysOfWeek getDaysOfWeek() {
+    public Alarm.DaysOfWeek getDaysOfWeek() {
         return mDaysOfWeek;
     }
 }
index bf783fb..7c36649 100644 (file)
@@ -44,7 +44,7 @@ import android.widget.Toast;
  * Manages each alarm
  */
 public class SetAlarm extends PreferenceActivity
-        implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener {
+        implements TimePickerDialog.OnTimeSetListener {
 
     private EditTextPreference mLabel;
     private Preference mTimePref;
@@ -58,11 +58,9 @@ public class SetAlarm extends PreferenceActivity
     private int mHour;
     private int mMinutes;
 
-    private boolean mReportAlarmCalled;
-
     /**
-     * Set an alarm.  Requires an Alarms.ID to be passed in as an
-     * extra
+     * Set an alarm.  Requires an Alarms.ALARM_ID to be passed in as an
+     * extra. FIXME: Pass an Alarm object like every other Activity.
      */
     @Override
     protected void onCreate(Bundle icicle) {
@@ -87,21 +85,22 @@ public class SetAlarm extends PreferenceActivity
         mRepeatPref = (RepeatPreference) findPreference("setRepeat");
 
         Intent i = getIntent();
-        mId = i.getIntExtra(Alarms.ID, -1);
+        mId = i.getIntExtra(Alarms.ALARM_ID, -1);
         if (Log.LOGV) {
             Log.v("In SetAlarm, alarm id = " + mId);
         }
 
-        mReportAlarmCalled = false;
         /* load alarm details from database */
-        Alarms.getAlarm(getContentResolver(), this, mId);
-        /* This should never happen, but does occasionally with the monkey.
-         * I believe it's a race condition where a deleted alarm is opened
-         * before the alarm list is refreshed. */
-        if (!mReportAlarmCalled) {
-            Log.e("reportAlarm never called!");
-            finish();
-        }
+        Alarm alarm = Alarms.getAlarm(getContentResolver(), mId);
+        mLabel.setText(alarm.label);
+        mLabel.setSummary(alarm.label);
+        mHour = alarm.hour;
+        mMinutes = alarm.minutes;
+        mRepeatPref.setDaysOfWeek(alarm.daysOfWeek);
+        mVibratePref.setChecked(alarm.vibrate);
+        // Give the alert uri to the preference.
+        mAlarmPref.setAlert(alarm.alert);
+        updateTime();
 
         // We have to do this to get the save/cancel buttons to highlight on
         // their own.
@@ -168,52 +167,6 @@ public class SetAlarm extends PreferenceActivity
         updateTime();
     }
 
-    /**
-     * Alarms.AlarmSettings implementation.  Database feeds current
-     * settings in through this call
-     */
-    public void reportAlarm(
-            int idx, boolean enabled, int hour, int minutes,
-            Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label,
-            String alert) {
-
-        mLabel.setText(label);
-        mLabel.setSummary(label);
-        mHour = hour;
-        mMinutes = minutes;
-        mRepeatPref.setDaysOfWeek(daysOfWeek);
-        mVibratePref.setChecked(vibrate);
-
-        Uri alertUri = null;
-        if (Alarms.ALARM_ALERT_SILENT.equals(alert)) {
-            if (Log.LOGV) {
-                Log.v("reportAlarm: silent alert");
-            }
-        } else {
-            if (alert != null && alert.length() != 0) {
-                alertUri = Uri.parse(alert);
-            }
-
-            // If the database alert is null or it failed to parse, use the
-            // default alert.
-            if (alertUri == null) {
-                alertUri = RingtoneManager.getDefaultUri(
-                        RingtoneManager.TYPE_ALARM);
-            }
-
-            if (Log.LOGV) {
-                Log.v("reportAlarm alert: " + alert + " uri: " + alertUri);
-            }
-        }
-
-        // Give the alert uri to the preference.
-        mAlarmPref.setAlert(alertUri);
-
-        updateTime();
-
-        mReportAlarmCalled = true;
-    }
-
     private void updateTime() {
         if (Log.LOGV) {
             Log.v("updateTime " + mId);
@@ -237,7 +190,7 @@ public class SetAlarm extends PreferenceActivity
      */
     private static void saveAlarm(
             Context context, int id, boolean enabled, int hour, int minute,
-            Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label,
+            Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String label,
             String alert, boolean popToast) {
         if (Log.LOGV) Log.v("** saveAlarm " + id + " " + label + " " + enabled
                 + " " + hour + " " + minute + " vibe " + vibrate);
@@ -256,7 +209,7 @@ public class SetAlarm extends PreferenceActivity
      * goes off.  This helps prevent "am/pm" mistakes.
      */
     static void popAlarmSetToast(Context context, int hour, int minute,
-                                 Alarms.DaysOfWeek daysOfWeek) {
+                                 Alarm.DaysOfWeek daysOfWeek) {
 
         String toastText = formatToast(context, hour, minute, daysOfWeek);
         Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG);
@@ -269,7 +222,7 @@ public class SetAlarm extends PreferenceActivity
      * now"
      */
     static String formatToast(Context context, int hour, int minute,
-                              Alarms.DaysOfWeek daysOfWeek) {
+                              Alarm.DaysOfWeek daysOfWeek) {
         long alarm = Alarms.calculateAlarm(hour, minute,
                                            daysOfWeek).getTimeInMillis();
         long delta = alarm - System.currentTimeMillis();;