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 import java.text.SimpleDateFormat; 31 import java.util.Date; 32 33 /** 34 * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert 35 * activity. Passes through Alarm ID. 36 */ 37 public class AlarmReceiver extends BroadcastReceiver { 38 39 /** If the alarm is older than STALE_WINDOW seconds, ignore. It 40 is probably the result of a time or timezone change */ 41 private final static int STALE_WINDOW = 60 * 30; 42 43 @Override onReceive(Context context, Intent intent)44 public void onReceive(Context context, Intent intent) { 45 if (Alarms.ALARM_KILLED.equals(intent.getAction())) { 46 // The alarm has been killed, update the notification 47 updateNotification(context, (Alarm) 48 intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), 49 intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); 50 return; 51 } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { 52 Alarms.saveSnoozeAlert(context, -1, -1); 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.v("AlarmReceiver failed to parse the alarm from the intent"); 71 return; 72 } 73 74 // Intentionally verbose: always log the alarm time to provide useful 75 // information in bug reports. 76 long now = System.currentTimeMillis(); 77 SimpleDateFormat format = 78 new SimpleDateFormat("HH:mm:ss.SSS aaa"); 79 Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor " 80 + format.format(new Date(alarm.time))); 81 82 if (now > alarm.time + STALE_WINDOW * 1000) { 83 if (Log.LOGV) { 84 Log.v("AlarmReceiver ignoring stale alarm"); 85 } 86 return; 87 } 88 89 // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can 90 // pick it up. 91 AlarmAlertWakeLock.acquireCpuWakeLock(context); 92 93 /* Close dialogs and window shade */ 94 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 95 context.sendBroadcast(closeDialogs); 96 97 // Decide which activity to start based on the state of the keyguard. 98 Class c = AlarmAlert.class; 99 KeyguardManager km = (KeyguardManager) context.getSystemService( 100 Context.KEYGUARD_SERVICE); 101 if (km.inKeyguardRestrictedInputMode()) { 102 // Use the full screen activity for security. 103 c = AlarmAlertFullScreen.class; 104 } 105 106 /* launch UI, explicitly stating that this is not due to user action 107 * so that the current app's notification management is not disturbed */ 108 Intent alarmAlert = new Intent(context, c); 109 alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 110 alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 111 | Intent.FLAG_ACTIVITY_NO_USER_ACTION); 112 context.startActivity(alarmAlert); 113 114 // Disable the snooze alert if this alarm is the snooze. 115 Alarms.disableSnoozeAlert(context, alarm.id); 116 // Disable this alarm if it does not repeat. 117 if (!alarm.daysOfWeek.isRepeatSet()) { 118 Alarms.enableAlarm(context, alarm.id, false); 119 } else { 120 // Enable the next alert if there is one. The above call to 121 // enableAlarm will call setNextAlert so avoid calling it twice. 122 Alarms.setNextAlert(context); 123 } 124 125 // Play the alarm alert and vibrate the device. 126 Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); 127 playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 128 context.startService(playAlarm); 129 130 // Trigger a notification that, when clicked, will show the alarm alert 131 // dialog. No need to check for fullscreen since this will always be 132 // launched from a user action. 133 Intent notify = new Intent(context, AlarmAlert.class); 134 notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 135 PendingIntent pendingNotify = PendingIntent.getActivity(context, 136 alarm.id, notify, 0); 137 138 // Use the alarm's label or the default label as the ticker text and 139 // main text of the notification. 140 String label = alarm.getLabelOrDefault(context); 141 Notification n = new Notification(R.drawable.stat_notify_alarm, 142 label, alarm.time); 143 n.setLatestEventInfo(context, label, 144 context.getString(R.string.alarm_notify_text), 145 pendingNotify); 146 n.flags |= Notification.FLAG_SHOW_LIGHTS 147 | Notification.FLAG_ONGOING_EVENT; 148 n.ledARGB = 0xFF00FF00; 149 n.ledOnMS = 500; 150 n.ledOffMS = 500; 151 152 // Send the notification using the alarm id to easily identify the 153 // correct notification. 154 NotificationManager nm = getNotificationManager(context); 155 nm.notify(alarm.id, n); 156 } 157 getNotificationManager(Context context)158 private NotificationManager getNotificationManager(Context context) { 159 return (NotificationManager) 160 context.getSystemService(Context.NOTIFICATION_SERVICE); 161 } 162 updateNotification(Context context, Alarm alarm, int timeout)163 private void updateNotification(Context context, Alarm alarm, int timeout) { 164 NotificationManager nm = getNotificationManager(context); 165 166 // If the alarm is null, just cancel the notification. 167 if (alarm == null) { 168 if (Log.LOGV) { 169 Log.v("Cannot update notification for killer callback"); 170 } 171 return; 172 } 173 174 // Launch SetAlarm when clicked. 175 Intent viewAlarm = new Intent(context, SetAlarm.class); 176 viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id); 177 PendingIntent intent = 178 PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); 179 180 // Update the notification to indicate that the alert has been 181 // silenced. 182 String label = alarm.getLabelOrDefault(context); 183 Notification n = new Notification(R.drawable.stat_notify_alarm, 184 label, alarm.time); 185 n.setLatestEventInfo(context, label, 186 context.getString(R.string.alarm_alert_alert_silenced, timeout), 187 intent); 188 n.flags |= Notification.FLAG_AUTO_CANCEL; 189 // We have to cancel the original notification since it is in the 190 // ongoing section and we want the "killed" notification to be a plain 191 // notification. 192 nm.cancel(alarm.id); 193 nm.notify(alarm.id, n); 194 } 195 } 196