Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 487d52c2 authored by Sara Ting's avatar Sara Ting
Browse files

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
parent a6ff5f05
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
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);
}
+0 −14
Original line number Diff line number Diff line
@@ -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
+9 −13
Original line number Diff line number Diff line
@@ -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) {
@@ -258,13 +258,9 @@ public class AlertReceiver extends BroadcastReceiver {
        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(
+41 −26
Original line number Diff line number Diff line
@@ -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,9 +666,15 @@ 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) {
    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<NotificationInfo> events) {
        StringBuilder digestTitle = new StringBuilder();
@@ -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();
+19 −8
Original line number Diff line number Diff line
@@ -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);
Loading