blob: 69f726bbba8a99289ba4c5122772b44eecf22bf3 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.os.PowerManager.WakeLock;
import java.util.Calendar;
/**
* Glue class: connects AlarmAlert IntentReceiver to AlarmAlert
* activity. Passes through Alarm ID.
*/
public class AlarmReceiver extends BroadcastReceiver {
/** If the alarm is older than STALE_WINDOW, ignore. It
is probably the result of a time or timezone change */
private final static int STALE_WINDOW = 30 * 60 * 1000;
@Override
public void onReceive(final Context context, final Intent intent) {
final PendingResult result = goAsync();
final WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
wl.acquire();
AsyncHandler.post(new Runnable() {
@Override public void run() {
handleIntent(context, intent);
result.finish();
wl.release();
}
});
}
private void handleIntent(Context context, Intent intent) {
if (Alarms.ALARM_KILLED.equals(intent.getAction())) {
// The alarm has been killed, update the notification
updateNotification(context, (Alarm)
intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA),
intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1));
return;
} else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) {
Alarm alarm = null;
if (intent.hasExtra(Alarms.ALARM_INTENT_EXTRA)) {
// Get the alarm out of the Intent
alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA);
}
if (alarm != null) {
Alarms.disableSnoozeAlert(context, alarm.id);
Alarms.setNextAlert(context);
} else {
// Don't know what snoozed alarm to cancel, so cancel them all. This
// shouldn't happen
Log.wtf("Unable to parse Alarm from intent.");
Alarms.saveSnoozeAlert(context, Alarms.INVALID_ALARM_ID, -1);
}
// Inform any active UI that alarm snooze was cancelled
context.sendBroadcast(new Intent(Alarms.ALARM_SNOOZE_CANCELLED));
return;
} else if (!Alarms.ALARM_ALERT_ACTION.equals(intent.getAction())) {
// Unknown intent, bail.
return;
}
Alarm alarm = null;
// Grab the alarm from the intent. Since the remote AlarmManagerService
// fills in the Intent to add some extra data, it must unparcel the
// Alarm object. It throws a ClassNotFoundException when unparcelling.
// To avoid this, do the marshalling ourselves.
final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA);
if (data != null) {
Parcel in = Parcel.obtain();
in.unmarshall(data, 0, data.length);
in.setDataPosition(0);
alarm = Alarm.CREATOR.createFromParcel(in);
}
if (alarm == null) {
Log.wtf("Failed to parse the alarm from the intent");
// Make sure we set the next alert if needed.
Alarms.setNextAlert(context);
return;
}
// Disable the snooze alert if this alarm is the snooze.
Alarms.disableSnoozeAlert(context, alarm.id);
// Disable this alarm if it does not repeat.
if (!alarm.daysOfWeek.isRepeatSet()) {
Alarms.enableAlarm(context, alarm.id, false);
} else {
// Enable the next alert if there is one. The above call to
// enableAlarm will call setNextAlert so avoid calling it twice.
Alarms.setNextAlert(context);
}
// Intentionally verbose: always log the alarm time to provide useful
// information in bug reports.
long now = System.currentTimeMillis();
Log.v("Received alarm set for id=" + alarm.id + " " + Log.formatTime(alarm.time));
// Always verbose to track down time change problems.
if (now > alarm.time + STALE_WINDOW) {
Log.v("Ignoring stale alarm");
return;
}
// Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can
// pick it up.
AlarmAlertWakeLock.acquireCpuWakeLock(context);
/* Close dialogs and window shade */
Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.sendBroadcast(closeDialogs);
// Decide which activity to start based on the state of the keyguard.
Class c = AlarmAlertFullScreen.class;
/*
KeyguardManager km = (KeyguardManager) context.getSystemService(
Context.KEYGUARD_SERVICE);
if (km.inKeyguardRestrictedInputMode()) {
// Use the full screen activity for security.
c = AlarmAlertFullScreen.class;
}
*/
// Play the alarm alert and vibrate the device.
Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION);
playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
context.startService(playAlarm);
// Trigger a notification that, when clicked, will show the alarm alert
// dialog. No need to check for fullscreen since this will always be
// launched from a user action.
Intent notify = new Intent(context, AlarmAlertFullScreen.class);
notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
PendingIntent pendingNotify = PendingIntent.getActivity(context,
alarm.id, notify, 0);
// These two notifications will be used for the action buttons on the notification.
Intent snoozeIntent = new Intent(Alarms.ALARM_SNOOZE_ACTION);
snoozeIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
PendingIntent pendingSnooze = PendingIntent.getBroadcast(context,
alarm.id, snoozeIntent, 0);
Intent dismissIntent = new Intent(Alarms.ALARM_DISMISS_ACTION);
dismissIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
PendingIntent pendingDismiss = PendingIntent.getBroadcast(context,
alarm.id, dismissIntent, 0);
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(alarm.time);
String alarmTime = Alarms.formatTime(context, cal);
// Use the alarm's label or the default label main text of the notification.
String label = alarm.getLabelOrDefault(context);
Notification n = new Notification.Builder(context)
.setContentTitle(label)
.setContentText(alarmTime)
.setSmallIcon(R.drawable.stat_notify_alarm)
.setOngoing(true)
.setAutoCancel(false)
.setPriority(Notification.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setWhen(0)
.addAction(R.drawable.stat_notify_alarm,
context.getResources().getString(R.string.alarm_alert_snooze_text),
pendingSnooze)
.addAction(android.R.drawable.ic_menu_close_clear_cancel,
context.getResources().getString(R.string.alarm_alert_dismiss_text),
pendingDismiss)
.build();
n.contentIntent = pendingNotify;
// NEW: Embed the full-screen UI here. The notification manager will
// take care of displaying it if it's OK to do so.
Intent alarmAlert = new Intent(context, c);
alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
n.fullScreenIntent = PendingIntent.getActivity(context, alarm.id, alarmAlert, 0);
// Send the notification using the alarm id to easily identify the
// correct notification.
NotificationManager nm = getNotificationManager(context);
nm.notify(alarm.id, n);
}
private NotificationManager getNotificationManager(Context context) {
return (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
}
private void updateNotification(Context context, Alarm alarm, int timeout) {
NotificationManager nm = getNotificationManager(context);
// If the alarm is null, just cancel the notification.
if (alarm == null) {
if (Log.LOGV) {
Log.v("Cannot update notification for killer callback");
}
return;
}
// Launch AlarmClock when clicked.
Intent viewAlarm = new Intent(context, AlarmClock.class);
viewAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
PendingIntent intent =
PendingIntent.getActivity(context, alarm.id, viewAlarm, 0);
// Update the notification to indicate that the alert has been
// silenced.
String label = alarm.getLabelOrDefault(context);
Notification n = new Notification(R.drawable.stat_notify_alarm,
label, alarm.time);
n.setLatestEventInfo(context, label,
context.getString(R.string.alarm_alert_alert_silenced, timeout),
intent);
n.flags |= Notification.FLAG_AUTO_CANCEL;
// We have to cancel the original notification since it is in the
// ongoing section and we want the "killed" notification to be a plain
// notification.
nm.cancel(alarm.id);
nm.notify(alarm.id, n);
}
}