Home | History | Annotate | Download | only in deskclock
      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