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.Notification; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Parcel; 26 import android.os.PowerManager.WakeLock; 27 28 import java.util.Calendar; 29 30 /** 31 * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert 32 * activity. Passes through Alarm ID. 33 */ 34 public class AlarmReceiver extends BroadcastReceiver { 35 36 /** If the alarm is older than STALE_WINDOW, ignore. It 37 is probably the result of a time or timezone change */ 38 private final static int STALE_WINDOW = 30 * 60 * 1000; 39 40 @Override onReceive(final Context context, final Intent intent)41 public void onReceive(final Context context, final Intent intent) { 42 final PendingResult result = goAsync(); 43 final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context); 44 wl.acquire(); 45 AsyncHandler.post(new Runnable() { 46 @Override public void run() { 47 handleIntent(context, intent); 48 result.finish(); 49 wl.release(); 50 } 51 }); 52 } 53 handleIntent(Context context, Intent intent)54 private void handleIntent(Context context, Intent intent) { 55 if (Alarms.ALARM_KILLED.equals(intent.getAction())) { 56 // The alarm has been killed, update the notification 57 updateNotification(context, (Alarm) 58 intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), 59 intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); 60 return; 61 } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { 62 Alarm alarm = null; 63 if (intent.hasExtra(Alarms.ALARM_INTENT_EXTRA)) { 64 // Get the alarm out of the Intent 65 alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 66 } 67 68 if (alarm != null) { 69 Alarms.disableSnoozeAlert(context, alarm.id); 70 Alarms.setNextAlert(context); 71 } else { 72 // Don't know what snoozed alarm to cancel, so cancel them all. This 73 // shouldn't happen 74 Log.wtf("Unable to parse Alarm from intent."); 75 Alarms.saveSnoozeAlert(context, Alarms.INVALID_ALARM_ID, -1); 76 } 77 // Inform any active UI that alarm snooze was cancelled 78 context.sendBroadcast(new Intent(Alarms.ALARM_SNOOZE_CANCELLED)); 79 return; 80 } else if (!Alarms.ALARM_ALERT_ACTION.equals(intent.getAction())) { 81 // Unknown intent, bail. 82 return; 83 } 84 85 Alarm alarm = null; 86 // Grab the alarm from the intent. Since the remote AlarmManagerService 87 // fills in the Intent to add some extra data, it must unparcel the 88 // Alarm object. It throws a ClassNotFoundException when unparcelling. 89 // To avoid this, do the marshalling ourselves. 90 final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); 91 if (data != null) { 92 Parcel in = Parcel.obtain(); 93 in.unmarshall(data, 0, data.length); 94 in.setDataPosition(0); 95 alarm = Alarm.CREATOR.createFromParcel(in); 96 } 97 98 if (alarm == null) { 99 Log.wtf("Failed to parse the alarm from the intent"); 100 // Make sure we set the next alert if needed. 101 Alarms.setNextAlert(context); 102 return; 103 } 104 105 // Disable the snooze alert if this alarm is the snooze. 106 Alarms.disableSnoozeAlert(context, alarm.id); 107 // Disable this alarm if it does not repeat. 108 if (!alarm.daysOfWeek.isRepeatSet()) { 109 Alarms.enableAlarm(context, alarm.id, false); 110 } else { 111 // Enable the next alert if there is one. The above call to 112 // enableAlarm will call setNextAlert so avoid calling it twice. 113 Alarms.setNextAlert(context); 114 } 115 116 // Intentionally verbose: always log the alarm time to provide useful 117 // information in bug reports. 118 long now = System.currentTimeMillis(); 119 Log.v("Received alarm set for id=" + alarm.id + " " + Log.formatTime(alarm.time)); 120 121 // Always verbose to track down time change problems. 122 if (now > alarm.time + STALE_WINDOW) { 123 Log.v("Ignoring stale alarm"); 124 return; 125 } 126 127 // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can 128 // pick it up. 129 AlarmAlertWakeLock.acquireCpuWakeLock(context); 130 131 /* Close dialogs and window shade */ 132 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 133 context.sendBroadcast(closeDialogs); 134 135 // Decide which activity to start based on the state of the keyguard. 136 Class c = AlarmAlertFullScreen.class; 137 /* 138 KeyguardManager km = (KeyguardManager) context.getSystemService( 139 Context.KEYGUARD_SERVICE); 140 if (km.inKeyguardRestrictedInputMode()) { 141 // Use the full screen activity for security. 142 c = AlarmAlertFullScreen.class; 143 } 144 */ 145 146 // Play the alarm alert and vibrate the device. 147 Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); 148 playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 149 context.startService(playAlarm); 150 151 // Trigger a notification that, when clicked, will show the alarm alert 152 // dialog. No need to check for fullscreen since this will always be 153 // launched from a user action. 154 Intent notify = new Intent(context, AlarmAlertFullScreen.class); 155 notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 156 PendingIntent pendingNotify = PendingIntent.getActivity(context, 157 alarm.id, notify, 0); 158 159 // These two notifications will be used for the action buttons on the notification. 160 Intent snoozeIntent = new Intent(Alarms.ALARM_SNOOZE_ACTION); 161 snoozeIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 162 PendingIntent pendingSnooze = PendingIntent.getBroadcast(context, 163 alarm.id, snoozeIntent, 0); 164 Intent dismissIntent = new Intent(Alarms.ALARM_DISMISS_ACTION); 165 dismissIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 166 PendingIntent pendingDismiss = PendingIntent.getBroadcast(context, 167 alarm.id, dismissIntent, 0); 168 169 final Calendar cal = Calendar.getInstance(); 170 cal.setTimeInMillis(alarm.time); 171 String alarmTime = Alarms.formatTime(context, cal); 172 173 // Use the alarm's label or the default label main text of the notification. 174 String label = alarm.getLabelOrDefault(context); 175 176 Notification n = new Notification.Builder(context) 177 .setContentTitle(label) 178 .setContentText(alarmTime) 179 .setSmallIcon(R.drawable.stat_notify_alarm) 180 .setOngoing(true) 181 .setAutoCancel(false) 182 .setPriority(Notification.PRIORITY_MAX) 183 .setDefaults(Notification.DEFAULT_LIGHTS) 184 .setWhen(0) 185 .addAction(R.drawable.stat_notify_alarm, 186 context.getResources().getString(R.string.alarm_alert_snooze_text), 187 pendingSnooze) 188 .addAction(android.R.drawable.ic_menu_close_clear_cancel, 189 context.getResources().getString(R.string.alarm_alert_dismiss_text), 190 pendingDismiss) 191 .build(); 192 n.contentIntent = pendingNotify; 193 194 // NEW: Embed the full-screen UI here. The notification manager will 195 // take care of displaying it if it's OK to do so. 196 Intent alarmAlert = new Intent(context, c); 197 alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 198 alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 199 | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 200 n.fullScreenIntent = PendingIntent.getActivity(context, alarm.id, alarmAlert, 0); 201 202 // Send the notification using the alarm id to easily identify the 203 // correct notification. 204 NotificationManager nm = getNotificationManager(context); 205 nm.notify(alarm.id, n); 206 } 207 getNotificationManager(Context context)208 private NotificationManager getNotificationManager(Context context) { 209 return (NotificationManager) 210 context.getSystemService(Context.NOTIFICATION_SERVICE); 211 } 212 updateNotification(Context context, Alarm alarm, int timeout)213 private void updateNotification(Context context, Alarm alarm, int timeout) { 214 NotificationManager nm = getNotificationManager(context); 215 216 // If the alarm is null, just cancel the notification. 217 if (alarm == null) { 218 if (Log.LOGV) { 219 Log.v("Cannot update notification for killer callback"); 220 } 221 return; 222 } 223 224 // Launch AlarmClock when clicked. 225 Intent viewAlarm = new Intent(context, AlarmClock.class); 226 viewAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 227 PendingIntent intent = 228 PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); 229 230 // Update the notification to indicate that the alert has been 231 // silenced. 232 String label = alarm.getLabelOrDefault(context); 233 Notification n = new Notification(R.drawable.stat_notify_alarm, 234 label, alarm.time); 235 n.setLatestEventInfo(context, label, 236 context.getString(R.string.alarm_alert_alert_silenced, timeout), 237 intent); 238 n.flags |= Notification.FLAG_AUTO_CANCEL; 239 // We have to cancel the original notification since it is in the 240 // ongoing section and we want the "killed" notification to be a plain 241 // notification. 242 nm.cancel(alarm.id); 243 nm.notify(alarm.id, n); 244 } 245 } 246