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 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 41 public void onReceive(Context context, Intent intent) { 42 if (Alarms.ALARM_KILLED.equals(intent.getAction())) { 43 // The alarm has been killed, update the notification 44 updateNotification(context, (Alarm) 45 intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), 46 intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); 47 return; 48 } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { 49 Alarms.saveSnoozeAlert(context, -1, -1); 50 return; 51 } else if (!Alarms.ALARM_ALERT_ACTION.equals(intent.getAction())) { 52 // Unknown intent, bail. 53 return; 54 } 55 56 Alarm alarm = null; 57 // Grab the alarm from the intent. Since the remote AlarmManagerService 58 // fills in the Intent to add some extra data, it must unparcel the 59 // Alarm object. It throws a ClassNotFoundException when unparcelling. 60 // To avoid this, do the marshalling ourselves. 61 final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); 62 if (data != null) { 63 Parcel in = Parcel.obtain(); 64 in.unmarshall(data, 0, data.length); 65 in.setDataPosition(0); 66 alarm = Alarm.CREATOR.createFromParcel(in); 67 } 68 69 if (alarm == null) { 70 Log.wtf("Failed to parse the alarm from the intent"); 71 // Make sure we set the next alert if needed. 72 Alarms.setNextAlert(context); 73 return; 74 } 75 76 // Disable the snooze alert if this alarm is the snooze. 77 Alarms.disableSnoozeAlert(context, alarm.id); 78 // Disable this alarm if it does not repeat. 79 if (!alarm.daysOfWeek.isRepeatSet()) { 80 Alarms.enableAlarm(context, alarm.id, false); 81 } else { 82 // Enable the next alert if there is one. The above call to 83 // enableAlarm will call setNextAlert so avoid calling it twice. 84 Alarms.setNextAlert(context); 85 } 86 87 // Intentionally verbose: always log the alarm time to provide useful 88 // information in bug reports. 89 long now = System.currentTimeMillis(); 90 Log.v("Recevied alarm set for " + Log.formatTime(alarm.time)); 91 92 // Always verbose to track down time change problems. 93 if (now > alarm.time + STALE_WINDOW) { 94 Log.v("Ignoring stale alarm"); 95 return; 96 } 97 98 // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can 99 // pick it up. 100 AlarmAlertWakeLock.acquireCpuWakeLock(context); 101 102 /* Close dialogs and window shade */ 103 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 104 context.sendBroadcast(closeDialogs); 105 106 // Decide which activity to start based on the state of the keyguard. 107 Class c = AlarmAlert.class; 108 KeyguardManager km = (KeyguardManager) context.getSystemService( 109 Context.KEYGUARD_SERVICE); 110 if (km.inKeyguardRestrictedInputMode()) { 111 // Use the full screen activity for security. 112 c = AlarmAlertFullScreen.class; 113 } 114 115 // Play the alarm alert and vibrate the device. 116 Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); 117 playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 118 context.startService(playAlarm); 119 120 // Trigger a notification that, when clicked, will show the alarm alert 121 // dialog. No need to check for fullscreen since this will always be 122 // launched from a user action. 123 Intent notify = new Intent(context, AlarmAlert.class); 124 notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 125 PendingIntent pendingNotify = PendingIntent.getActivity(context, 126 alarm.id, notify, 0); 127 128 // Use the alarm's label or the default label as the ticker text and 129 // main text of the notification. 130 String label = alarm.getLabelOrDefault(context); 131 Notification n = new Notification(R.drawable.stat_notify_alarm, 132 label, alarm.time); 133 n.setLatestEventInfo(context, label, 134 context.getString(R.string.alarm_notify_text), 135 pendingNotify); 136 n.flags |= Notification.FLAG_SHOW_LIGHTS 137 | Notification.FLAG_ONGOING_EVENT; 138 n.defaults |= Notification.DEFAULT_LIGHTS; 139 140 // NEW: Embed the full-screen UI here. The notification manager will 141 // take care of displaying it if it's OK to do so. 142 Intent alarmAlert = new Intent(context, c); 143 alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 144 alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 145 | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 146 n.fullScreenIntent = PendingIntent.getActivity(context, alarm.id, alarmAlert, 0); 147 148 // Send the notification using the alarm id to easily identify the 149 // correct notification. 150 NotificationManager nm = getNotificationManager(context); 151 nm.notify(alarm.id, n); 152 } 153 154 private NotificationManager getNotificationManager(Context context) { 155 return (NotificationManager) 156 context.getSystemService(Context.NOTIFICATION_SERVICE); 157 } 158 159 private void updateNotification(Context context, Alarm alarm, int timeout) { 160 NotificationManager nm = getNotificationManager(context); 161 162 // If the alarm is null, just cancel the notification. 163 if (alarm == null) { 164 if (Log.LOGV) { 165 Log.v("Cannot update notification for killer callback"); 166 } 167 return; 168 } 169 170 // Launch SetAlarm when clicked. 171 Intent viewAlarm = new Intent(context, SetAlarm.class); 172 viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id); 173 PendingIntent intent = 174 PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); 175 176 // Update the notification to indicate that the alert has been 177 // silenced. 178 String label = alarm.getLabelOrDefault(context); 179 Notification n = new Notification(R.drawable.stat_notify_alarm, 180 label, alarm.time); 181 n.setLatestEventInfo(context, label, 182 context.getString(R.string.alarm_alert_alert_silenced, timeout), 183 intent); 184 n.flags |= Notification.FLAG_AUTO_CANCEL; 185 // We have to cancel the original notification since it is in the 186 // ongoing section and we want the "killed" notification to be a plain 187 // notification. 188 nm.cancel(alarm.id); 189 nm.notify(alarm.id, n); 190 } 191 } 192