From 487d52c2789114e0ee3e7ce85694611b8d59dd70 Mon Sep 17 00:00:00 2001 From: Sara Ting Date: Fri, 25 May 2012 10:26:49 -0700 Subject: Notifications: made alarm scheduling testable, and a couple minor fixes. - Added tests for scheduling next notification refresh time - Fixed priority of single expired event notifications (fixed from DEFAULT to MIN) - Fixed automatic demoting of allday events (refresh time wasn't correct) Bug:6282451 Change-Id: I160736827fc0b1017e2d001cb0fdb8f7d0502339 --- .../calendar/alerts/AlarmManagerInterface.java | 10 ++++ src/com/android/calendar/alerts/AlertActivity.java | 14 ----- src/com/android/calendar/alerts/AlertReceiver.java | 22 +++---- src/com/android/calendar/alerts/AlertService.java | 67 +++++++++++++--------- src/com/android/calendar/alerts/AlertUtils.java | 27 ++++++--- 5 files changed, 79 insertions(+), 61 deletions(-) create mode 100644 src/com/android/calendar/alerts/AlarmManagerInterface.java (limited to 'src') diff --git a/src/com/android/calendar/alerts/AlarmManagerInterface.java b/src/com/android/calendar/alerts/AlarmManagerInterface.java new file mode 100644 index 00000000..5ee83734 --- /dev/null +++ b/src/com/android/calendar/alerts/AlarmManagerInterface.java @@ -0,0 +1,10 @@ +package com.android.calendar.alerts; + +import android.app.PendingIntent; + +/** + * AlarmManager abstracted to an interface for testability. + */ +public interface AlarmManagerInterface { + public void set(int type, long triggerAtMillis, PendingIntent operation); +} diff --git a/src/com/android/calendar/alerts/AlertActivity.java b/src/com/android/calendar/alerts/AlertActivity.java index d22408fd..12d7b10a 100644 --- a/src/com/android/calendar/alerts/AlertActivity.java +++ b/src/com/android/calendar/alerts/AlertActivity.java @@ -123,20 +123,6 @@ public class AlertActivity extends Activity implements OnClickListener { } } - @Override - protected void onInsertComplete(int token, Object cookie, Uri uri) { - if (uri != null) { - Long alarmTime = (Long) cookie; - - if (alarmTime != 0) { - // Set a new alarm to go off after the snooze delay. - // TODO make provider schedule this automatically when - // inserting an alarm - AlertUtils.scheduleAlarm(AlertActivity.this, null, alarmTime); - } - } - } - @Override protected void onUpdateComplete(int token, Object cookie, int result) { // Ignore diff --git a/src/com/android/calendar/alerts/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java index 16c42b50..1991a4d1 100644 --- a/src/com/android/calendar/alerts/AlertReceiver.java +++ b/src/com/android/calendar/alerts/AlertReceiver.java @@ -219,16 +219,16 @@ public class AlertReceiver extends BroadcastReceiver { public static NotificationWrapper makeBasicNotification(Context context, String title, String summaryText, long startMillis, long endMillis, long eventId, - int notificationId, boolean doPopup) { + int notificationId, boolean doPopup, int priority) { Notification n = buildBasicNotification(new Notification.Builder(context), context, title, summaryText, startMillis, endMillis, eventId, notificationId, - doPopup, false, false); + doPopup, priority, false); return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup); } private static Notification buildBasicNotification(Notification.Builder notificationBuilder, Context context, String title, String summaryText, long startMillis, long endMillis, - long eventId, int notificationId, boolean doPopup, boolean highPriority, + long eventId, int notificationId, boolean doPopup, int priority, boolean addActionButtons) { Resources resources = context.getResources(); if (title == null || title.length() == 0) { @@ -256,15 +256,11 @@ public class AlertReceiver extends BroadcastReceiver { // Turn off timestamp. notificationBuilder.setWhen(0); - + if (Utils.isJellybeanOrLater()) { - // Setting to a higher priority will encourage notification manager to expand the - // notification. - if (highPriority) { - notificationBuilder.setPriority(Notification.PRIORITY_HIGH); - } else { - notificationBuilder.setPriority(Notification.PRIORITY_DEFAULT); - } + // Should be one of the values in Notification (ie. Notification.PRIORITY_HIGH, etc). + // A higher priority will encourage notification manager to expand it. + notificationBuilder.setPriority(priority); } if (addActionButtons) { @@ -313,11 +309,11 @@ public class AlertReceiver extends BroadcastReceiver { */ public static NotificationWrapper makeExpandingNotification(Context context, String title, String summaryText, String description, long startMillis, long endMillis, long eventId, - int notificationId, boolean doPopup, boolean highPriority) { + int notificationId, boolean doPopup, int priority) { Notification.Builder basicBuilder = new Notification.Builder(context); Notification notification = buildBasicNotification(basicBuilder, context, title, summaryText, startMillis, endMillis, eventId, notificationId, doPopup, - highPriority, true); + priority, true); if (Utils.isJellybeanOrLater()) { // Create a new-style expanded notification Notification.BigTextStyle expandedBuilder = new Notification.BigTextStyle( diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java index 8a4563e5..15986af6 100644 --- a/src/com/android/calendar/alerts/AlertService.java +++ b/src/com/android/calendar/alerts/AlertService.java @@ -16,7 +16,6 @@ package com.android.calendar.alerts; -import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.Service; @@ -248,12 +247,13 @@ public class AlertService extends Service { return false; } - return generateAlerts(context, nm, prefs, alertCursor, currentTime, MAX_NOTIFICATIONS); + return generateAlerts(context, nm, AlertUtils.createAlarmManager(context), prefs, + alertCursor, currentTime, MAX_NOTIFICATIONS); } public static boolean generateAlerts(Context context, NotificationMgr nm, - SharedPreferences prefs, Cursor alertCursor, final long currentTime, - final int maxNotifications) { + AlarmManagerInterface alarmMgr, SharedPreferences prefs, Cursor alertCursor, + final long currentTime, final int maxNotifications) { if (DEBUG) { Log.d(TAG, "alertCursor count:" + alertCursor.getCount()); } @@ -326,7 +326,8 @@ public class AlertService extends Service { info.allDay, info.location); notification = AlertReceiver.makeBasicNotification(context, info.eventName, summaryText, info.startMillis, info.endMillis, info.eventId, - AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, false); + AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, false, + Notification.PRIORITY_MIN); } else { // Multiple expired events are listed in a digest. notification = AlertReceiver.makeDigestNotification(context, @@ -366,7 +367,7 @@ public class AlertService extends Service { // Schedule the next silent refresh time so notifications will change // buckets (eg. drop into expired digest, etc). if (nextRefreshTime < Long.MAX_VALUE && nextRefreshTime > currentTime) { - AlertUtils.scheduleNextNotificationRefresh(context, null, nextRefreshTime); + AlertUtils.scheduleNextNotificationRefresh(context, alarmMgr, nextRefreshTime); if (DEBUG) { long minutesBeforeRefresh = (nextRefreshTime - currentTime) / MINUTE_MS; Time time = new Time(); @@ -455,18 +456,27 @@ public class AlertService extends Service { } private static long getNextRefreshTime(NotificationInfo info, long currentTime) { - // We change an event's priority bucket at 15 minutes into the event (so recently started - // concurrent events stay high priority) + long startAdjustedForAllDay = info.startMillis; + long endAdjustedForAllDay = info.endMillis; + if (info.allDay) { + Time t = new Time(); + startAdjustedForAllDay = Utils.convertAlldayUtcToLocal(t, info.startMillis, + Time.getCurrentTimezone()); + endAdjustedForAllDay = Utils.convertAlldayUtcToLocal(t, info.startMillis, + Time.getCurrentTimezone()); + } + + // We change an event's priority bucket at 15 minutes into the event or 1/4 event duration. long nextRefreshTime = Long.MAX_VALUE; - long gracePeriodCutoff = info.startMillis + - getGracePeriodMs(info.startMillis, info.endMillis); + long gracePeriodCutoff = startAdjustedForAllDay + + getGracePeriodMs(startAdjustedForAllDay, endAdjustedForAllDay, info.allDay); if (gracePeriodCutoff > currentTime) { nextRefreshTime = Math.min(nextRefreshTime, gracePeriodCutoff); } // ... and at the end (so expiring ones drop into a digest). - if (info.endMillis > currentTime && info.endMillis > gracePeriodCutoff) { - nextRefreshTime = Math.min(nextRefreshTime, info.endMillis); + if (endAdjustedForAllDay > currentTime && endAdjustedForAllDay > gracePeriodCutoff) { + nextRefreshTime = Math.min(nextRefreshTime, endAdjustedForAllDay); } return nextRefreshTime; } @@ -578,16 +588,12 @@ public class AlertService extends Service { // Adjust for all day events to ensure the right bucket. Don't use the 1/4 event // duration grace period for these. - long gracePeriodMs; long beginTimeAdjustedForAllDay = beginTime; String tz = null; if (allDay) { tz = TimeZone.getDefault().getID(); beginTimeAdjustedForAllDay = Utils.convertAlldayUtcToLocal(null, beginTime, tz); - gracePeriodMs = MIN_DEPRIORITIZE_GRACE_PERIOD_MS; - } else { - gracePeriodMs = getGracePeriodMs(beginTime, endTime); } // Handle multiple alerts for the same event ID. @@ -636,7 +642,8 @@ public class AlertService extends Service { // TODO: Prioritize by "primary" calendar eventIds.put(eventId, newInfo); - long highPriorityCutoff = currentTime - gracePeriodMs; + long highPriorityCutoff = currentTime - + getGracePeriodMs(beginTime, endTime, allDay); if (beginTimeAdjustedForAllDay > highPriorityCutoff) { // High priority = future events or events that just started @@ -659,8 +666,14 @@ public class AlertService extends Service { /** * High priority cutoff should be 1/4 event duration or 15 min, whichever is longer. */ - private static long getGracePeriodMs(long beginTime, long endTime) { - return Math.max(MIN_DEPRIORITIZE_GRACE_PERIOD_MS, ((endTime - beginTime) / 4)); + private static long getGracePeriodMs(long beginTime, long endTime, boolean allDay) { + if (allDay) { + // We don't want all day events to be high priority for hours, so automatically + // demote these after 15 min. + return MIN_DEPRIORITIZE_GRACE_PERIOD_MS; + } else { + return Math.max(MIN_DEPRIORITIZE_GRACE_PERIOD_MS, ((endTime - beginTime) / 4)); + } } private static String getDigestTitle(ArrayList events) { @@ -679,11 +692,15 @@ public class AlertService extends Service { private static void postNotification(NotificationInfo info, String summaryText, Context context, boolean highPriority, NotificationPrefs prefs, NotificationMgr notificationMgr, int notificationId) { + int priorityVal = Notification.PRIORITY_DEFAULT; + if (highPriority) { + priorityVal = Notification.PRIORITY_HIGH; + } + String tickerText = getTickerText(info.eventName, info.location); NotificationWrapper notification = AlertReceiver.makeExpandingNotification(context, info.eventName, summaryText, info.description, info.startMillis, - info.endMillis, info.eventId, notificationId, prefs.getDoPopup(), - highPriority); + info.endMillis, info.eventId, notificationId, prefs.getDoPopup(), priorityVal); boolean quietUpdate = true; String ringtone = NotificationPrefs.EMPTY_RINGTONE; @@ -851,10 +868,8 @@ public class AlertService extends Service { private void doTimeChanged() { ContentResolver cr = getContentResolver(); - Object service = getSystemService(Context.ALARM_SERVICE); - AlarmManager manager = (AlarmManager) service; // TODO Move this into Provider - rescheduleMissedAlarms(cr, this, manager); + rescheduleMissedAlarms(cr, this, AlertUtils.createAlarmManager(this)); updateAlertNotification(this); } @@ -883,8 +898,8 @@ public class AlertService extends Service { * @param context the Context * @param manager the AlarmManager */ - public static final void rescheduleMissedAlarms(ContentResolver cr, Context context, - AlarmManager manager) { + private static final void rescheduleMissedAlarms(ContentResolver cr, Context context, + AlarmManagerInterface manager) { // Get all the alerts that have been scheduled but have not fired // and should have fired by now and are not too old. long now = System.currentTimeMillis(); diff --git a/src/com/android/calendar/alerts/AlertUtils.java b/src/com/android/calendar/alerts/AlertUtils.java index 1777a2d4..cb5a33ca 100644 --- a/src/com/android/calendar/alerts/AlertUtils.java +++ b/src/com/android/calendar/alerts/AlertUtils.java @@ -52,6 +52,20 @@ public class AlertUtils { public static final String NOTIFICATION_ID_KEY = "notificationid"; public static final String EVENT_IDS_KEY = "eventids"; + /** + * Creates an AlarmManagerInterface that wraps a real AlarmManager. The alarm code + * was abstracted to an interface to make it testable. + */ + public static AlarmManagerInterface createAlarmManager(Context context) { + final AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + return new AlarmManagerInterface() { + @Override + public void set(int type, long triggerAtMillis, PendingIntent operation) { + mgr.set(type, triggerAtMillis, operation); + } + }; + } + /** * Schedules an alarm intent with the system AlarmManager that will notify * listeners when a reminder should be fired. The provider will keep @@ -63,7 +77,8 @@ public class AlertUtils { * @param manager The AlarmManager to use or null * @param alarmTime The time to fire the intent in UTC millis since epoch */ - public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) { + public static void scheduleAlarm(Context context, AlarmManagerInterface manager, + long alarmTime) { scheduleAlarmHelper(context, manager, alarmTime, false); } @@ -71,17 +86,13 @@ public class AlertUtils { * Schedules the next alarm to silently refresh the notifications. Note that if there * is a pending silent refresh alarm, it will be replaced with this one. */ - static void scheduleNextNotificationRefresh(Context context, AlarmManager manager, + static void scheduleNextNotificationRefresh(Context context, AlarmManagerInterface manager, long alarmTime) { scheduleAlarmHelper(context, manager, alarmTime, true); } - private static void scheduleAlarmHelper(Context context, AlarmManager manager, long alarmTime, - boolean quietUpdate) { - if (manager == null) { - manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - } - + private static void scheduleAlarmHelper(Context context, AlarmManagerInterface manager, + long alarmTime, boolean quietUpdate) { int alarmType = AlarmManager.RTC_WAKEUP; Intent intent = new Intent(CalendarContract.ACTION_EVENT_REMINDER); intent.setClass(context, AlertReceiver.class); -- cgit v1.2.3