1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.deskclock; 18 19 import android.app.KeyguardManager; 20 import android.app.Notification; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.ContentUris; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.BroadcastReceiver; 27 import android.database.Cursor; 28 import android.os.Parcel; 29 import android.os.PowerManager.WakeLock; 30 31 /** 32 * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert 33 * activity. Passes through Alarm ID. 34 */ 35 public class AlarmReceiver extends BroadcastReceiver { 36 37 /** If the alarm is older than STALE_WINDOW, ignore. It 38 is probably the result of a time or timezone change */ 39 private final static int STALE_WINDOW = 30 * 60 * 1000; 40 41 @Override 42 public void onReceive(final Context context, final Intent intent) { 43 final PendingResult result = goAsync(); 44 final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context); 45 wl.acquire(); 46 AsyncHandler.post(new Runnable() { 47 @Override public void run() { 48 handleIntent(context, intent); 49 result.finish(); 50 wl.release(); 51 } 52 }); 53 } 54 55 private void handleIntent(Context context, Intent intent) { 56 if (Alarms.ALARM_KILLED.equals(intent.getAction())) { 57 // The alarm has been killed, update the notification 58 updateNotification(context, (Alarm) 59 intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), 60 intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); 61 return; 62 } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { 63 Alarm alarm = null; 64 if (intent.hasExtra(Alarms.ALARM_INTENT_EXTRA)) { 65 // Get the alarm out of the Intent 66 alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 67 } 68 69 if (alarm != null) { 70 Alarms.disableSnoozeAlert(context, alarm.id); 71 Alarms.setNextAlert(context); 72 } else { 73 // Don't know what snoozed alarm to cancel, so cancel them all. This 74 // shouldn't happen 75 Log.wtf("Unable to parse Alarm from intent."); 76 Alarms.saveSnoozeAlert(context, Alarms.INVALID_ALARM_ID, -1); 77 } 78 return; 79 } else if (!Alarms.ALARM_ALERT_ACTION.equals(intent.getAction())) { 80 // Unknown intent, bail. 81 return; 82 } 83 84 Alarm alarm = null; 85 // Grab the alarm from the intent. Since the remote AlarmManagerService 86 // fills in the Intent to add some extra data, it must unparcel the 87 // Alarm object. It throws a ClassNotFoundException when unparcelling. 88 // To avoid this, do the marshalling ourselves. 89 final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); 90 if (data != null) { 91 Parcel in = Parcel.obtain(); 92 in.unmarshall(data, 0, data.length); 93 in.setDataPosition(0); 94 alarm = Alarm.CREATOR.createFromParcel(in); 95 } 96 97 if (alarm == null) { 98 Log.wtf("Failed to parse the alarm from the intent"); 99 // Make sure we set the next alert if needed. 100 Alarms.setNextAlert(context); 101 return; 102 } 103 104 // Disable the snooze alert if this alarm is the snooze. 105 Alarms.disableSnoozeAlert(context, alarm.id); 106 // Disable this alarm if it does not repeat. 107 if (!alarm.daysOfWeek.isRepeatSet()) { 108 Alarms.enableAlarm(context, alarm.id, false); 109 } else { 110 // Enable the next alert if there is one. The above call to 111 // enableAlarm will call setNextAlert so avoid calling it twice. 112 Alarms.setNextAlert(context); 113 } 114 115 // Intentionally verbose: always log the alarm time to provide useful 116 // information in bug reports. 117 long now = System.currentTimeMillis(); 118 Log.v("Recevied alarm set for " + Log.formatTime(alarm.time)); 119 120 // Always verbose to track down time change problems. 121 if (now > alarm.time + STALE_WINDOW) { 122 Log.v("Ignoring stale alarm"); 123 return; 124 } 125 126 // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can 127 // pick it up. 128 AlarmAlertWakeLock.acquireCpuWakeLock(context); 129 130 /* Close dialogs and window shade */ 131 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 132 context.sendBroadcast(closeDialogs); 133 134 // Decide which activity to start based on the state of the keyguard. 135 Class c = AlarmAlert.class; 136 KeyguardManager km = (KeyguardManager) context.getSystemService( 137 Context.KEYGUARD_SERVICE); 138 if (km.inKeyguardRestrictedInputMode()) { 139 // Use the full screen activity for security. 140 c = AlarmAlertFullScreen.class; 141 } 142 143 // Play the alarm alert and vibrate the device. 144 Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); 145 playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 146 context.startService(playAlarm); 147 148 // Trigger a notification that, when clicked, will show the alarm alert 149 // dialog. No need to check for fullscreen since this will always be 150 // launched from a user action. 151 Intent notify = new Intent(context, AlarmAlert.class); 152 notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 153 PendingIntent pendingNotify = PendingIntent.getActivity(context, 154 alarm.id, notify, 0); 155 156 // Use the alarm's label or the default label as the ticker text and 157 // main text of the notification. 158 String label = alarm.getLabelOrDefault(context); 159 Notification n = new Notification(R.drawable.stat_notify_alarm, 160 label, alarm.time); 161 n.setLatestEventInfo(context, label, 162 context.getString(R.string.alarm_notify_text), 163 pendingNotify); 164 n.flags |= Notification.FLAG_SHOW_LIGHTS 165 | Notification.FLAG_ONGOING_EVENT; 166 n.defaults |= Notification.DEFAULT_LIGHTS; 167 168 // NEW: Embed the full-screen UI here. The notification manager will 169 // take care of displaying it if it's OK to do so. 170 Intent alarmAlert = new Intent(context, c); 171 alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 172 alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 173 | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 174 n.fullScreenIntent = PendingIntent.getActivity(context, alarm.id, alarmAlert, 0); 175 176 // Send the notification using the alarm id to easily identify the 177 // correct notification. 178 NotificationManager nm = getNotificationManager(context); 179 nm.notify(alarm.id, n); 180 } 181 182 private NotificationManager getNotificationManager(Context context) { 183 return (NotificationManager) 184 context.getSystemService(Context.NOTIFICATION_SERVICE); 185 } 186 187 private void updateNotification(Context context, Alarm alarm, int timeout) { 188 NotificationManager nm = getNotificationManager(context); 189 190 // If the alarm is null, just cancel the notification. 191 if (alarm == null) { 192 if (Log.LOGV) { 193 Log.v("Cannot update notification for killer callback"); 194 } 195 return; 196 } 197 198 // Launch SetAlarm when clicked. 199 Intent viewAlarm = new Intent(context, SetAlarm.class); 200 viewAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 201 PendingIntent intent = 202 PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); 203 204 // Update the notification to indicate that the alert has been 205 // silenced. 206 String label = alarm.getLabelOrDefault(context); 207 Notification n = new Notification(R.drawable.stat_notify_alarm, 208 label, alarm.time); 209 n.setLatestEventInfo(context, label, 210 context.getString(R.string.alarm_alert_alert_silenced, timeout), 211 intent); 212 n.flags |= Notification.FLAG_AUTO_CANCEL; 213 // We have to cancel the original notification since it is in the 214 // ongoing section and we want the "killed" notification to be a plain 215 // notification. 216 nm.cancel(alarm.id); 217 nm.notify(alarm.id, n); 218 } 219 } 220