• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onReceive(final Context context, final Intent intent)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 
handleIntent(Context context, Intent intent)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 
getNotificationManager(Context context)182     private NotificationManager getNotificationManager(Context context) {
183         return (NotificationManager)
184                 context.getSystemService(Context.NOTIFICATION_SERVICE);
185     }
186 
updateNotification(Context context, Alarm alarm, int timeout)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