*/
public class AlertService extends Service {
private static final String TAG = "AlertService";
-
+
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
-
- private static final String[] ALERT_PROJECTION = new String[] {
+
+ private static final String[] ALERT_PROJECTION = new String[] {
CalendarAlerts._ID, // 0
CalendarAlerts.EVENT_ID, // 1
CalendarAlerts.STATE, // 2
};
// We just need a simple projection that returns any column
- private static final String[] ALERT_PROJECTION_SMALL = new String[] {
+ private static final String[] ALERT_PROJECTION_SMALL = new String[] {
CalendarAlerts._ID, // 0
};
-
+
private static final int ALERT_INDEX_ID = 0;
private static final int ALERT_INDEX_EVENT_ID = 1;
private static final int ALERT_INDEX_STATE = 2;
private static final int INSTANCES_INDEX_END = 1;
// We just need a simple projection that returns any column
- private static final String[] REMINDER_PROJECTION_SMALL = new String[] {
+ private static final String[] REMINDER_PROJECTION_SMALL = new String[] {
Reminders._ID, // 0
};
-
+
+ private final boolean alarmsFiredRecently(ContentResolver cr) {
+ String selection = CalendarAlerts.RECEIVED_TIME + ">="
+ + (System.currentTimeMillis() - 10000);
+ String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
+ Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection, selection, null, null);
+
+ boolean recentAlarms = false;
+ if (cursor != null) {
+ if (cursor.moveToFirst() && cursor.getCount() > 0) {
+ recentAlarms = true;
+ }
+ cursor.close();
+ }
+ return recentAlarms;
+ }
+
@SuppressWarnings("deprecation")
void processMessage(Message msg) {
Bundle bundle = (Bundle) msg.obj;
-
+
// On reboot, update the notification bar with the contents of the
// CalendarAlerts table.
String action = bundle.getString("action");
Log.d(TAG, "uri: " + alertUri);
}
+ ContentResolver cr = getContentResolver();
+ boolean alarmsFiredRecently = alarmsFiredRecently(cr);
+
if (alertUri != null) {
+ if (!Calendar.AUTHORITY.equals(alertUri.getAuthority())) {
+ Log.w(TAG, "Invalid AUTHORITY uri: " + alertUri);
+ return;
+ }
+
// Record the received time in the CalendarAlerts table.
// This is useful for finding bugs that cause alarms to be
// missed or delayed.
ContentValues values = new ContentValues();
values.put(CalendarAlerts.RECEIVED_TIME, System.currentTimeMillis());
- getContentResolver().update(alertUri, values, null /* where */, null /* args */);
+ cr.update(alertUri, values, null /* where */, null /* args */);
}
-
- ContentResolver cr = getContentResolver();
+
Cursor alertCursor = cr.query(alertUri, ALERT_PROJECTION,
null /* selection */, null, null /* sort order */);
-
+
long alertId, eventId, alarmTime;
int minutes;
String eventName;
location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
- declined = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS) ==
+ declined = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS) ==
Attendees.ATTENDEE_STATUS_DECLINED;
-
+
// If the event was declined, then mark the alarm DISMISSED,
// otherwise, mark the alarm FIRED.
int newState = CalendarAlerts.FIRED;
alertCursor.close();
}
}
-
+
// Do not show an alert if the event was declined
if (declined) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
}
return;
}
-
+
long beginTime = bundle.getLong(Calendar.EVENT_BEGIN_TIME, 0);
long endTime = bundle.getLong(Calendar.EVENT_END_TIME, 0);
-
+
// Check if this alarm is still valid. The time of the event may
// have been changed, or the reminder may have been changed since
// this alarm was set. First, search for an instance in the Instances
instanceCursor.close();
}
}
-
+
// Check that a reminder for this event exists with the same number
// of minutes. But snoozed alarms have minutes = 0, so don't do this
// check for snoozed alarms.
}
}
}
-
+
// If the event time was changed and the event has already ended,
// then don't sound the alarm.
if (alarmTime > instanceEnd) {
}
return;
}
-
+
// Check for another alarm in the CalendarAlerts table that has the
// same event id and the same "minutes". This can occur
// if the event start time was changed to an earlier time and the
}
}
}
-
+
// Find all the alerts that have fired but have not been dismissed
selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION, selection, null);
-
+
if (alertCursor == null || alertCursor.getCount() == 0) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "no fired alarms found");
} finally {
alertCursor.close();
}
-
+
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "creating new alarm notification, numReminders: " + numReminders);
}
Notification notification = AlertReceiver.makeNewAlertNotification(this, eventName,
location, numReminders);
-
+
// Generate either a pop-up dialog, status bar notification, or
// neither. Pop-up dialog and status bar notification may include a
// sound, an alert, or both. A status bar notification also includes
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String reminderType = prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_TYPE,
CalendarPreferenceActivity.ALERT_TYPE_STATUS_BAR);
-
+
if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_OFF)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "alert preference is OFF");
}
return;
}
-
- NotificationManager nm =
+
+ NotificationManager nm =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- boolean reminderVibrate =
+ boolean reminderVibrate =
prefs.getBoolean(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE, false);
- String reminderRingtone =
- prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_RINGTONE, null);
// Possibly generate a vibration
if (reminderVibrate) {
notification.defaults |= Notification.DEFAULT_VIBRATE;
}
-
- // Possibly generate a sound. If 'Silent' is chosen, the ringtone string will be empty.
- notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
- .parse(reminderRingtone);
-
+
+ // Temp fix. If we sounded an notification recently, be quiet so the
+ // audio won't overlap.
+
+ // TODO Long term fix: CalendarProvider currently setup an alarm with
+ // AlarmManager for each event notification. So AlertService can post
+ // multiple notifications back to back if there are multiple alarms that
+ // fire at the same time. Instead of doing that, CalendarProvider should
+ // setup one alarm for each wake up time. AlertService can query for
+ // alerts table and update notification manager only once.
+ if (!alarmsFiredRecently) {
+ // Possibly generate a sound. If 'Silent' is chosen, the ringtone
+ // string will be empty.
+ String reminderRingtone = prefs.getString(
+ CalendarPreferenceActivity.KEY_ALERTS_RINGTONE, null);
+ notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
+ .parse(reminderRingtone);
+ } else {
+ notification.sound = null;
+ }
+
if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_ALERTS)) {
Intent alertIntent = new Intent();
alertIntent.setClass(this, AlertActivity.class);
LayoutInflater inflater;
inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.alert_toast, null);
-
+
AlertAdapter.updateView(this, view, eventName, location, beginTime, endTime, allDay);
}
-
+
// Record the notify time in the CalendarAlerts table.
// This is used for debugging missed alarms.
ContentValues values = new ContentValues();
long currentTime = System.currentTimeMillis();
values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
cr.update(alertUri, values, null /* where */, null /* args */);
-
+
// The notification time should be pretty close to the reminder time
// that the user set for this event. If the notification is late, then
// that's a bug and we should log an error.
nm.notify(0, notification);
}
-
+
private void doTimeChanged() {
ContentResolver cr = getContentResolver();
Object service = getSystemService(Context.ALARM_SERVICE);
CalendarAlerts.rescheduleMissedAlarms(cr, this, manager);
AlertReceiver.updateAlertNotification(this);
}
-
+
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
-
+
@Override
public void handleMessage(Message msg) {
processMessage(msg);
// NOTE: We MUST not call stopSelf() directly, since we need to
// make sure the wake lock acquired by AlertReceiver is released.
AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
- }
+ }
};
@Override
HandlerThread thread = new HandlerThread("AlertService",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
-
+
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
- public void onStart(Intent intent, int startId) {
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- msg.obj = intent.getExtras();
- mServiceHandler.sendMessage(msg);
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = intent.getExtras();
+ mServiceHandler.sendMessage(msg);
+ }
+ return START_REDELIVER_INTENT;
}
@Override