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.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.os.Parcel;
     26 import android.os.PowerManager.WakeLock;
     27 
     28 import java.util.Calendar;
     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(final Context context, final Intent intent) {
     42         final PendingResult result = goAsync();
     43         final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
     44         wl.acquire();
     45         AsyncHandler.post(new Runnable() {
     46             @Override public void run() {
     47                 handleIntent(context, intent);
     48                 result.finish();
     49                 wl.release();
     50             }
     51         });
     52     }
     53 
     54     private void handleIntent(Context context, Intent intent) {
     55         if (Alarms.ALARM_KILLED.equals(intent.getAction())) {
     56             // The alarm has been killed, update the notification
     57             updateNotification(context, (Alarm)
     58                     intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA),
     59                     intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1));
     60             return;
     61         } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) {
     62             Alarm alarm = null;
     63             if (intent.hasExtra(Alarms.ALARM_INTENT_EXTRA)) {
     64                 // Get the alarm out of the Intent
     65                 alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
     66             }
     67 
     68             if (alarm != null) {
     69                 Alarms.disableSnoozeAlert(context, alarm.id);
     70                 Alarms.setNextAlert(context);
     71             } else {
     72                 // Don't know what snoozed alarm to cancel, so cancel them all.  This
     73                 // shouldn't happen
     74                 Log.wtf("Unable to parse Alarm from intent.");
     75                 Alarms.saveSnoozeAlert(context, Alarms.INVALID_ALARM_ID, -1);
     76             }
     77             // Inform any active UI that alarm snooze was cancelled
     78             context.sendBroadcast(new Intent(Alarms.ALARM_SNOOZE_CANCELLED));
     79             return;
     80         } else if (!Alarms.ALARM_ALERT_ACTION.equals(intent.getAction())) {
     81             // Unknown intent, bail.
     82             return;
     83         }
     84 
     85         Alarm alarm = null;
     86         // Grab the alarm from the intent. Since the remote AlarmManagerService
     87         // fills in the Intent to add some extra data, it must unparcel the
     88         // Alarm object. It throws a ClassNotFoundException when unparcelling.
     89         // To avoid this, do the marshalling ourselves.
     90         final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA);
     91         if (data != null) {
     92             Parcel in = Parcel.obtain();
     93             in.unmarshall(data, 0, data.length);
     94             in.setDataPosition(0);
     95             alarm = Alarm.CREATOR.createFromParcel(in);
     96         }
     97 
     98         if (alarm == null) {
     99             Log.wtf("Failed to parse the alarm from the intent");
    100             // Make sure we set the next alert if needed.
    101             Alarms.setNextAlert(context);
    102             return;
    103         }
    104 
    105         // Disable the snooze alert if this alarm is the snooze.
    106         Alarms.disableSnoozeAlert(context, alarm.id);
    107         // Disable this alarm if it does not repeat.
    108         if (!alarm.daysOfWeek.isRepeatSet()) {
    109             Alarms.enableAlarm(context, alarm.id, false);
    110         } else {
    111             // Enable the next alert if there is one. The above call to
    112             // enableAlarm will call setNextAlert so avoid calling it twice.
    113             Alarms.setNextAlert(context);
    114         }
    115 
    116         // Intentionally verbose: always log the alarm time to provide useful
    117         // information in bug reports.
    118         long now = System.currentTimeMillis();
    119         Log.v("Received alarm set for id=" + alarm.id + " " + Log.formatTime(alarm.time));
    120 
    121         // Always verbose to track down time change problems.
    122         if (now > alarm.time + STALE_WINDOW) {
    123             Log.v("Ignoring stale alarm");
    124             return;
    125         }
    126 
    127         // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can
    128         // pick it up.
    129         AlarmAlertWakeLock.acquireCpuWakeLock(context);
    130 
    131         /* Close dialogs and window shade */
    132         Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    133         context.sendBroadcast(closeDialogs);
    134 
    135         // Decide which activity to start based on the state of the keyguard.
    136         Class c = AlarmAlertFullScreen.class;
    137         /*
    138         KeyguardManager km = (KeyguardManager) context.getSystemService(
    139                 Context.KEYGUARD_SERVICE);
    140         if (km.inKeyguardRestrictedInputMode()) {
    141             // Use the full screen activity for security.
    142             c = AlarmAlertFullScreen.class;
    143         }
    144         */
    145 
    146         // Play the alarm alert and vibrate the device.
    147         Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION);
    148         playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    149         context.startService(playAlarm);
    150 
    151         // Trigger a notification that, when clicked, will show the alarm alert
    152         // dialog. No need to check for fullscreen since this will always be
    153         // launched from a user action.
    154         Intent notify = new Intent(context, AlarmAlertFullScreen.class);
    155         notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    156         PendingIntent pendingNotify = PendingIntent.getActivity(context,
    157                 alarm.id, notify, 0);
    158 
    159         // These two notifications will be used for the action buttons on the notification.
    160         Intent snoozeIntent = new Intent(Alarms.ALARM_SNOOZE_ACTION);
    161         snoozeIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    162         PendingIntent pendingSnooze = PendingIntent.getBroadcast(context,
    163                 alarm.id, snoozeIntent, 0);
    164         Intent dismissIntent = new Intent(Alarms.ALARM_DISMISS_ACTION);
    165         dismissIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    166         PendingIntent pendingDismiss = PendingIntent.getBroadcast(context,
    167                 alarm.id, dismissIntent, 0);
    168 
    169         final Calendar cal = Calendar.getInstance();
    170         cal.setTimeInMillis(alarm.time);
    171         String alarmTime = Alarms.formatTime(context, cal);
    172 
    173         // Use the alarm's label or the default label main text of the notification.
    174         String label = alarm.getLabelOrDefault(context);
    175 
    176         Notification n = new Notification.Builder(context)
    177         .setContentTitle(label)
    178         .setContentText(alarmTime)
    179         .setSmallIcon(R.drawable.stat_notify_alarm)
    180         .setOngoing(true)
    181         .setAutoCancel(false)
    182         .setPriority(Notification.PRIORITY_MAX)
    183         .setDefaults(Notification.DEFAULT_LIGHTS)
    184         .setWhen(0)
    185         .addAction(R.drawable.stat_notify_alarm,
    186                 context.getResources().getString(R.string.alarm_alert_snooze_text),
    187                 pendingSnooze)
    188         .addAction(android.R.drawable.ic_menu_close_clear_cancel,
    189                 context.getResources().getString(R.string.alarm_alert_dismiss_text),
    190                 pendingDismiss)
    191         .build();
    192         n.contentIntent = pendingNotify;
    193 
    194         // NEW: Embed the full-screen UI here. The notification manager will
    195         // take care of displaying it if it's OK to do so.
    196         Intent alarmAlert = new Intent(context, c);
    197         alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    198         alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    199                 | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
    200         n.fullScreenIntent = PendingIntent.getActivity(context, alarm.id, alarmAlert, 0);
    201 
    202         // Send the notification using the alarm id to easily identify the
    203         // correct notification.
    204         NotificationManager nm = getNotificationManager(context);
    205         nm.notify(alarm.id, n);
    206     }
    207 
    208     private NotificationManager getNotificationManager(Context context) {
    209         return (NotificationManager)
    210                 context.getSystemService(Context.NOTIFICATION_SERVICE);
    211     }
    212 
    213     private void updateNotification(Context context, Alarm alarm, int timeout) {
    214         NotificationManager nm = getNotificationManager(context);
    215 
    216         // If the alarm is null, just cancel the notification.
    217         if (alarm == null) {
    218             if (Log.LOGV) {
    219                 Log.v("Cannot update notification for killer callback");
    220             }
    221             return;
    222         }
    223 
    224         // Launch AlarmClock when clicked.
    225         Intent viewAlarm = new Intent(context, AlarmClock.class);
    226         viewAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
    227         PendingIntent intent =
    228                 PendingIntent.getActivity(context, alarm.id, viewAlarm, 0);
    229 
    230         // Update the notification to indicate that the alert has been
    231         // silenced.
    232         String label = alarm.getLabelOrDefault(context);
    233         Notification n = new Notification(R.drawable.stat_notify_alarm,
    234                 label, alarm.time);
    235         n.setLatestEventInfo(context, label,
    236                 context.getString(R.string.alarm_alert_alert_silenced, timeout),
    237                 intent);
    238         n.flags |= Notification.FLAG_AUTO_CANCEL;
    239         // We have to cancel the original notification since it is in the
    240         // ongoing section and we want the "killed" notification to be a plain
    241         // notification.
    242         nm.cancel(alarm.id);
    243         nm.notify(alarm.id, n);
    244     }
    245 }
    246