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 
     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