AlertServiceTest.java revision b5a2a61208857805cc053c6ac476d30100289279
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.calendar.alerts;
18
19import static android.app.Notification.PRIORITY_DEFAULT;
20import static android.app.Notification.PRIORITY_HIGH;
21import static android.app.Notification.PRIORITY_MIN;
22
23import android.content.SharedPreferences;
24import android.database.MatrixCursor;
25import android.provider.CalendarContract.Attendees;
26import android.provider.CalendarContract.CalendarAlerts;
27import android.test.AndroidTestCase;
28import android.test.suitebuilder.annotation.SmallTest;
29import android.test.suitebuilder.annotation.Smoke;
30import android.text.format.DateUtils;
31import android.text.format.Time;
32
33import com.android.calendar.GeneralPreferences;
34import com.android.calendar.alerts.AlertService.NotificationInfo;
35import com.android.calendar.alerts.AlertService.NotificationWrapper;
36
37import junit.framework.Assert;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Map;
42import java.util.Set;
43
44public class AlertServiceTest extends AndroidTestCase {
45
46    class MockSharedPreferences implements SharedPreferences {
47
48        /* "always", "silent", depends on ringer mode */
49        private String mVibrateWhen;
50        private String mRingtone;
51        private Boolean mPopup;
52
53        // Strict mode will fail if a preference key is queried more than once.
54        private boolean mStrict = false;
55
56        MockSharedPreferences() {
57            this(false);
58        }
59
60        MockSharedPreferences(boolean strict) {
61            super();
62            init();
63            this.mStrict = strict;
64        }
65
66        void init() {
67            mVibrateWhen = "always";
68            mRingtone = "/some/cool/ringtone";
69            mPopup = true;
70        }
71
72        @Override
73        public boolean contains(String key) {
74            if (GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN.equals(key)) {
75                return true;
76            }
77            return false;
78        }
79
80        @Override
81        public boolean getBoolean(String key, boolean defValue) {
82            if (GeneralPreferences.KEY_ALERTS_POPUP.equals(key)) {
83                if (mPopup == null) {
84                    Assert.fail(GeneralPreferences.KEY_ALERTS_POPUP + " fetched more than once.");
85                }
86                boolean val = mPopup;
87                if (mStrict) {
88                    mPopup = null;
89                }
90                return val;
91            }
92            throw new IllegalArgumentException();
93        }
94
95        @Override
96        public String getString(String key, String defValue) {
97            if (GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN.equals(key)) {
98                if (mVibrateWhen == null) {
99                    Assert.fail(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN
100                            + " fetched more than once.");
101                }
102                String val = mVibrateWhen;
103                if (mStrict) {
104                    mVibrateWhen = null;
105                }
106                return val;
107            }
108            if (GeneralPreferences.KEY_ALERTS_RINGTONE.equals(key)) {
109                if (mRingtone == null) {
110                    Assert.fail(GeneralPreferences.KEY_ALERTS_RINGTONE
111                            + " fetched more than once.");
112                }
113                String val = mRingtone;
114                if (mStrict) {
115                    mRingtone = null;
116                }
117                return val;
118            }
119            throw new IllegalArgumentException();
120        }
121
122        @Override
123        public Map<String, ?> getAll() {
124            throw new IllegalArgumentException();
125        }
126
127        @Override
128        public Set<String> getStringSet(String key, Set<String> defValues) {
129            throw new IllegalArgumentException();
130        }
131
132        @Override
133        public int getInt(String key, int defValue) {
134            throw new IllegalArgumentException();
135        }
136
137        @Override
138        public long getLong(String key, long defValue) {
139            throw new IllegalArgumentException();
140        }
141
142        @Override
143        public float getFloat(String key, float defValue) {
144            throw new IllegalArgumentException();
145        }
146
147        @Override
148        public Editor edit() {
149            throw new IllegalArgumentException();
150        }
151
152        @Override
153        public void registerOnSharedPreferenceChangeListener(
154                OnSharedPreferenceChangeListener listener) {
155            throw new IllegalArgumentException();
156        }
157
158        @Override
159        public void unregisterOnSharedPreferenceChangeListener(
160                OnSharedPreferenceChangeListener listener) {
161            throw new IllegalArgumentException();
162        }
163
164    }
165
166    // Created these constants so the test cases are shorter
167    public static final int SCHEDULED = CalendarAlerts.STATE_SCHEDULED;
168    public static final int FIRED = CalendarAlerts.STATE_FIRED;
169    public static final int DISMISSED = CalendarAlerts.STATE_DISMISSED;
170
171    public static final int ACCEPTED = Attendees.ATTENDEE_STATUS_ACCEPTED;
172    public static final int DECLINED = Attendees.ATTENDEE_STATUS_DECLINED;
173    public static final int INVITED = Attendees.ATTENDEE_STATUS_INVITED;
174    public static final int TENTATIVE = Attendees.ATTENDEE_STATUS_TENTATIVE;
175
176    class NotificationInstance {
177        int mAlertId;
178        int[] mAlertIdsInDigest;
179        int mPriority;
180
181        public NotificationInstance(int alertId, int priority) {
182            mAlertId = alertId;
183            mPriority = priority;
184        }
185
186        public NotificationInstance(int[] alertIdsInDigest, int priority) {
187            mAlertIdsInDigest = alertIdsInDigest;
188            mPriority = priority;
189        }
190    }
191
192    class Alert {
193        long mEventId;
194        int mAlertStatus;
195        int mResponseStatus;
196        int mAllDay;
197        long mBegin;
198        long mEnd;
199        int mMinute;
200        long mAlarmTime;
201
202        public Alert(long eventId, int alertStatus, int responseStatus, int allDay, long begin,
203                long end, int minute, long alarmTime) {
204            mEventId = eventId;
205            mAlertStatus = alertStatus;
206            mResponseStatus = responseStatus;
207            mAllDay = allDay;
208            mBegin = begin;
209            mEnd = end;
210            mMinute = minute;
211            mAlarmTime = alarmTime;
212        }
213
214    }
215
216    class AlertsTable {
217
218        ArrayList<Alert> mAlerts = new ArrayList<Alert>();
219
220        int addAlertRow(long eventId, int alertStatus, int responseStatus, int allDay, long begin,
221                long end, long alarmTime) {
222            Alert a = new Alert(eventId, alertStatus, responseStatus, allDay, begin, end,
223                    5 /* minute */, alarmTime);
224            int id = mAlerts.size();
225            mAlerts.add(a);
226            return id;
227        }
228
229        public MatrixCursor getAlertCursor() {
230            MatrixCursor alertCursor = new MatrixCursor(AlertService.ALERT_PROJECTION);
231
232            int i = 0;
233            for (Alert a : mAlerts) {
234                Object[] ca = {
235                        i++,
236                        a.mEventId,
237                        a.mAlertStatus,
238                        "Title" + a.mEventId + " " + a.mMinute,
239                        "Loc" + a.mEventId,
240                        a.mResponseStatus,
241                        a.mAllDay,
242                        a.mAlarmTime > 0 ? a.mAlarmTime : a.mBegin - a.mMinute * 60 * 1000,
243                        a.mMinute,
244                        a.mBegin,
245                        a.mEnd,
246                        "Desc: " + a.mAlarmTime
247                };
248                alertCursor.addRow(ca);
249            }
250            return alertCursor;
251        }
252
253    }
254
255    class NotificationTestManager implements NotificationMgr {
256        // Expected notifications
257        NotificationInstance[] mNotifications;
258
259        // Flag to know which notification has been posted or canceled
260        boolean[] mDone;
261
262        // CalendarAlerts table
263        private ArrayList<Alert> mAlerts;
264
265        public NotificationTestManager(ArrayList<Alert> alerts, int maxNotifications) {
266            assertEquals(0, AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID);
267            mAlerts = alerts;
268            mNotifications = new NotificationInstance[maxNotifications + 1];
269        }
270
271        public void expectTestNotification(int notificationId, int alertId, int highPriority) {
272            mNotifications[notificationId] = new NotificationInstance(alertId, highPriority);
273        }
274
275        public void expectTestNotification(int notificationId, int[] alertIds, int priority) {
276            mNotifications[notificationId] = new NotificationInstance(alertIds, priority);
277        }
278
279        private void verifyNotification(int id, NotificationWrapper nw) {
280                assertEquals(mNotifications[id].mPriority, nw.mNotification.priority);
281
282                NotificationInstance expected = mNotifications[id];
283                if (expected.mAlertIdsInDigest == null) {
284                    Alert a = mAlerts.get(expected.mAlertId);
285                    assertEquals("Event ID", a.mEventId, nw.mEventId);
286                    assertEquals("Begin time", a.mBegin, nw.mBegin);
287                    assertEquals("End time", a.mEnd, nw.mEnd);
288                } else {
289                    // Notification should be a digest.
290                    assertNotNull("Posted notification not a digest as expected.", nw.mNw);
291                    assertEquals("Number of notifications in digest not as expected.",
292                            expected.mAlertIdsInDigest.length, nw.mNw.size());
293                    for (int i = 0; i < nw.mNw.size(); i++) {
294                        Alert a = mAlerts.get(expected.mAlertIdsInDigest[i]);
295                        assertEquals("Digest item " + i + ": Event ID not as expected",
296                                a.mEventId, nw.mNw.get(i).mEventId);
297                        assertEquals("Digest item " + i + ": Begin time in digest",
298                                a.mBegin, nw.mNw.get(i).mBegin);
299                        assertEquals("Digest item " + i + ": End time in digest",
300                                a.mEnd, nw.mNw.get(i).mEnd);
301                    }
302                }
303        }
304
305        public void validateNotificationsAndReset() {
306            for (int i = 0; i < mDone.length; i++) {
307                assertTrue("Notification id " + i + " has not been posted", mDone[i]);
308            }
309            Arrays.fill(mDone, false);
310            Arrays.fill(mNotifications, null);
311        }
312
313        ///////////////////////////////
314        // NotificationMgr methods
315        @Override
316        public void cancel(int id) {
317            if (mDone == null) {
318                mDone = new boolean[mNotifications.length];
319            }
320            assertTrue("id out of bound: " + id, 0 <= id);
321            assertTrue("id out of bound: " + id, id < mDone.length);
322            assertFalse("id already used", mDone[id]);
323            mDone[id] = true;
324            assertNull("Unexpected cancel for id " + id, mNotifications[id]);
325        }
326
327        @Override
328        public void cancel(String tag, int id) {
329            throw new IllegalArgumentException();
330        }
331
332        @Override
333        public void cancelAll() {
334            for (int i = 0; i < mNotifications.length; i++) {
335                assertNull("Expecting notification id " + i + ". Got cancelAll", mNotifications[i]);
336
337                if (mDone != null) {
338                    assertFalse("Notification id " + i + " is done but got cancelAll", mDone[i]);
339                }
340            }
341
342            assertNull(mDone); // this should have been null since nothing
343                               // should have been posted
344            mDone = new boolean[mNotifications.length];
345            Arrays.fill(mDone, true);
346        }
347
348        @Override
349        public void notify(int id, NotificationWrapper nw) {
350            if (mDone == null) {
351                mDone = new boolean[mNotifications.length];
352            }
353            assertTrue("id out of bound: " + id, 0 <= id);
354            assertTrue("id out of bound: " + id, id < mDone.length);
355            assertFalse("id already used", mDone[id]);
356            mDone[id] = true;
357
358            assertNotNull("Unexpected notify for id " + id, mNotifications[id]);
359
360            verifyNotification(id, nw);
361        }
362
363        @Override
364        public void notify(String tag, int id, NotificationWrapper nw) {
365            throw new IllegalArgumentException();
366        }
367    }
368
369    // TODO
370    // Catch updates of new state, notify time, and received time
371    // Test ringer, vibrate,
372    // Test digest notifications
373    // Test intents, action email
374    // Catch alarmmgr calls
375
376    @Smoke
377    @SmallTest
378    public void testNoAlerts() {
379        MockSharedPreferences prefs = new MockSharedPreferences();
380        AlertsTable at = new AlertsTable();
381        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts,
382                AlertService.MAX_NOTIFICATIONS);
383
384        // Test no alert
385        long currentTime = 1000000;
386        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
387                AlertService.MAX_NOTIFICATIONS);
388        ntm.validateNotificationsAndReset();
389    }
390
391    @Smoke
392    @SmallTest
393    public void testSingleAlert() {
394        MockSharedPreferences prefs = new MockSharedPreferences();
395        AlertsTable at = new AlertsTable();
396        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts,
397                AlertService.MAX_NOTIFICATIONS);
398
399        int id = at.addAlertRow(100, SCHEDULED, ACCEPTED, 0 /* all day */, 1300000, 2300000, 0);
400
401        // Test one up coming alert
402        long currentTime = 1000000;
403        ntm.expectTestNotification(1, id, PRIORITY_HIGH);
404
405        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
406                AlertService.MAX_NOTIFICATIONS);
407        ntm.validateNotificationsAndReset(); // This wipes out notification
408                                             // tests added so far
409
410        // Test half way into an event
411        currentTime = 2300000;
412        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_DEFAULT);
413
414        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
415                AlertService.MAX_NOTIFICATIONS);
416        ntm.validateNotificationsAndReset();
417
418        // Test event ended
419        currentTime = 4300000;
420        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_MIN);
421
422        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
423                AlertService.MAX_NOTIFICATIONS);
424        ntm.validateNotificationsAndReset();
425    }
426
427    @SmallTest
428    public void testMultipleAlerts() {
429        int maxNotifications = 10;
430        MockSharedPreferences prefs = new MockSharedPreferences();
431        AlertsTable at = new AlertsTable();
432        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
433
434        // Current time - 5:00
435        long currentTime = createTimeInMillis(5, 0);
436
437        // Set up future alerts.  The real query implementation sorts by descending start
438        // time so simulate that here with our order of adds to AlertsTable.
439        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
440                createTimeInMillis(10, 0), 0);
441        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
442                createTimeInMillis(9, 0), 0);
443        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
444                createTimeInMillis(8, 0), 0);
445
446        // Set up concurrent alerts (that started recently).
447        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
448                createTimeInMillis(5, 40), 0);
449        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
450                createTimeInMillis(7, 30), 0);
451        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
452                createTimeInMillis(4, 50), 0);
453
454        // Set up past alerts.
455        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
456                createTimeInMillis(4, 0), 0);
457        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
458                createTimeInMillis(3, 0), 0);
459        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
460                createTimeInMillis(2, 0), 0);
461
462        // Check posted notifications.  The order listed here is the order simulates the
463        // order in the real notification bar (last one posted appears on top), so these
464        // should be lowest start time on top.
465        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
466        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
467        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
468        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
469        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
470        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
471        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
472                new int[] {id3, id2, id1}, PRIORITY_MIN);
473        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
474                maxNotifications);
475        ntm.validateNotificationsAndReset();
476
477        // Increase time by 15 minutes to check that some concurrent events dropped
478        // to the low priority bucket.
479        currentTime = createTimeInMillis(5, 15);
480        ntm.expectTestNotification(4, id5, PRIORITY_HIGH); // concurrent
481        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
482        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
483        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
484        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
485                new int[] {id6, id4, id3, id2, id1}, PRIORITY_MIN);
486        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
487                maxNotifications);
488        ntm.validateNotificationsAndReset();
489
490        // Increase time so some of the previously future ones change state.
491        currentTime = createTimeInMillis(8, 15);
492        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
493        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
494                new int[] {id8, id7, id6, id5, id4, id3, id2, id1}, PRIORITY_MIN);
495        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
496                maxNotifications);
497        ntm.validateNotificationsAndReset();
498    }
499
500    @SmallTest
501    public void testMultipleAlerts_max() {
502        MockSharedPreferences prefs = new MockSharedPreferences();
503        AlertsTable at = new AlertsTable();
504
505        // Current time - 5:00
506        long currentTime = createTimeInMillis(5, 0);
507
508        // Set up future alerts.  The real query implementation sorts by descending start
509        // time so simulate that here with our order of adds to AlertsTable.
510        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
511                createTimeInMillis(10, 0), 0);
512        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
513                createTimeInMillis(9, 0), 0);
514        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
515                createTimeInMillis(8, 0), 0);
516
517        // Set up concurrent alerts (that started recently).
518        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
519                createTimeInMillis(5, 40), 0);
520        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
521                createTimeInMillis(7, 30), 0);
522        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
523                createTimeInMillis(4, 50), 0);
524
525        // Set up past alerts.
526        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
527                createTimeInMillis(4, 0), 0);
528        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
529                createTimeInMillis(3, 0), 0);
530        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
531                createTimeInMillis(2, 0), 0);
532
533        // Test when # alerts = max.
534        int maxNotifications = 6;
535        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
536        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
537        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
538        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
539        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
540        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
541        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
542        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
543                new int[] {id3, id2, id1}, PRIORITY_MIN);
544        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
545                maxNotifications);
546        ntm.validateNotificationsAndReset();
547
548        // Test when # alerts > max.
549        maxNotifications = 4;
550        ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
551        ntm.expectTestNotification(4, id4, PRIORITY_HIGH); // concurrent
552        ntm.expectTestNotification(3, id5, PRIORITY_HIGH); // concurrent
553        ntm.expectTestNotification(2, id6, PRIORITY_HIGH); // concurrent
554        ntm.expectTestNotification(1, id7, PRIORITY_HIGH); // future
555        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
556                new int[] {id9, id8, id3, id2, id1}, PRIORITY_MIN);
557        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
558                maxNotifications);
559        ntm.validateNotificationsAndReset();
560    }
561
562    /**
563     * Test that the SharedPreferences are only fetched once for each setting.
564     */
565    @SmallTest
566    public void testMultipleAlerts_sharedPreferences() {
567        int maxNotifications = 10;
568        MockSharedPreferences prefs = new MockSharedPreferences(true /* strict mode */);
569        AlertsTable at = new AlertsTable();
570        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
571
572        // Current time - 5:00
573        long currentTime = createTimeInMillis(5, 0);
574
575        // Set up future alerts.  The real query implementation sorts by descending start
576        // time so simulate that here with our order of adds to AlertsTable.
577        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
578                createTimeInMillis(10, 0), 0);
579        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
580                createTimeInMillis(9, 0), 0);
581        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
582                createTimeInMillis(8, 0), 0);
583
584        // Set up concurrent alerts (that started recently).
585        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
586                createTimeInMillis(5, 40), 0);
587        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
588                createTimeInMillis(7, 30), 0);
589        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
590                createTimeInMillis(4, 50), 0);
591
592        // Set up past alerts.
593        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
594                createTimeInMillis(4, 0), 0);
595        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
596                createTimeInMillis(3, 0), 0);
597        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
598                createTimeInMillis(2, 0), 0);
599
600        // Expected notifications.
601        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
602        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
603        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
604        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
605        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
606        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
607        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
608                new int[] {id3, id2, id1}, PRIORITY_MIN);
609
610        // If this does not result in a failure (MockSharedPreferences fails for duplicate
611        // queries), then test passes.
612        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
613                maxNotifications);
614        ntm.validateNotificationsAndReset();
615    }
616
617    private NotificationInfo createNotificationInfo(long eventId) {
618        return new NotificationInfo("eventName", "location", "description", 100L, 200L, eventId,
619                false, false);
620    }
621
622    private static long createTimeInMillis(int hour, int minute) {
623        return createTimeInMillis(0 /* second */, minute, hour, 1 /* day */, 1 /* month */,
624                2012 /* year */, Time.getCurrentTimezone());
625    }
626
627    private static long createTimeInMillis(int second, int minute, int hour, int monthDay,
628            int month, int year, String timezone) {
629        Time t = new Time(timezone);
630        t.set(second, minute, hour, monthDay, month, year);
631        t.normalize(false);
632        return t.toMillis(false);
633    }
634
635    @SmallTest
636    public void testProcessQuery_skipDeclinedDismissed() {
637        int declinedEventId = 1;
638        int dismissedEventId = 2;
639        int acceptedEventId = 3;
640        long acceptedStartTime = createTimeInMillis(10, 0);
641        long acceptedEndTime = createTimeInMillis(10, 30);
642
643        AlertsTable at = new AlertsTable();
644        at.addAlertRow(declinedEventId, SCHEDULED, DECLINED, 0, createTimeInMillis(9, 0),
645                createTimeInMillis(10, 0), 0);
646        at.addAlertRow(dismissedEventId, SCHEDULED, DISMISSED, 0, createTimeInMillis(9, 30),
647                createTimeInMillis(11, 0), 0);
648        at.addAlertRow(acceptedEventId, SCHEDULED, ACCEPTED, 1, acceptedStartTime, acceptedEndTime,
649                0);
650
651        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
652        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
653        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
654        long currentTime = createTimeInMillis(5, 0);
655        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
656                mediumPriority, lowPriority);
657
658        assertEquals(0, lowPriority.size());
659        assertEquals(0, mediumPriority.size());
660        assertEquals(1, highPriority.size());
661        assertEquals(acceptedEventId, highPriority.get(0).eventId);
662        assertEquals(acceptedStartTime, highPriority.get(0).startMillis);
663        assertEquals(acceptedEndTime, highPriority.get(0).endMillis);
664        assertTrue(highPriority.get(0).allDay);
665    }
666
667    @SmallTest
668    public void testProcessQuery_newAlert() {
669        int scheduledAlertEventId = 1;
670        int firedAlertEventId = 2;
671
672        AlertsTable at = new AlertsTable();
673        at.addAlertRow(scheduledAlertEventId, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
674                createTimeInMillis(10, 0), 0);
675        at.addAlertRow(firedAlertEventId, FIRED, ACCEPTED, 0, createTimeInMillis(10, 0),
676                createTimeInMillis(10, 30), 0);
677
678        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
679        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
680        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
681        long currentTime = createTimeInMillis(5, 0);
682        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
683                mediumPriority, lowPriority);
684
685        assertEquals(0, lowPriority.size());
686        assertEquals(0, mediumPriority.size());
687        assertEquals(2, highPriority.size());
688        assertEquals(scheduledAlertEventId, highPriority.get(0).eventId);
689        assertTrue("newAlert should be ON for scheduled alerts", highPriority.get(0).newAlert);
690        assertEquals(firedAlertEventId, highPriority.get(1).eventId);
691        assertFalse("newAlert should be OFF for fired alerts", highPriority.get(1).newAlert);
692    }
693
694    @SmallTest
695    public void testProcessQuery_recurringEvent() {
696        int eventId = 1;
697        long earlierStartTime = createTimeInMillis(10, 0);
698        long laterStartTime = createTimeInMillis(11, 0);
699
700        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
701        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
702        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
703
704        AlertsTable at = new AlertsTable();
705        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 0, laterStartTime,
706                laterStartTime + DateUtils.HOUR_IN_MILLIS, 0);
707        at.addAlertRow(eventId, FIRED, ACCEPTED, 0, earlierStartTime,
708                earlierStartTime + DateUtils.HOUR_IN_MILLIS, 0);
709
710        // Both events in the future: the earliest one should be chosen.
711        long currentTime = earlierStartTime - DateUtils.DAY_IN_MILLIS * 5;
712        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
713                mediumPriority, lowPriority);
714        assertEquals(0, lowPriority.size());
715        assertEquals(0, mediumPriority.size());
716        assertEquals(1, highPriority.size());
717        assertEquals("Recurring event with earlier start time expected", earlierStartTime,
718                highPriority.get(0).startMillis);
719
720        // Increment time just past the earlier event: the earlier one should be chosen.
721        highPriority.clear();
722        currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 10;
723        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
724                mediumPriority, lowPriority);
725        assertEquals(0, lowPriority.size());
726        assertEquals(0, mediumPriority.size());
727        assertEquals(1, highPriority.size());
728        assertEquals("Recurring event with earlier start time expected", earlierStartTime,
729                highPriority.get(0).startMillis);
730
731        // Increment time to 15 min past the earlier event: the later one should be chosen.
732        highPriority.clear();
733        currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 15;
734        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
735                mediumPriority, lowPriority);
736        assertEquals(0, lowPriority.size());
737        assertEquals(0, mediumPriority.size());
738        assertEquals(1, highPriority.size());
739        assertEquals("Recurring event with later start time expected", laterStartTime,
740                highPriority.get(0).startMillis);
741
742        // Both events in the past: the later one should be chosen (in the low priority bucket).
743        highPriority.clear();
744        currentTime = laterStartTime + DateUtils.DAY_IN_MILLIS * 5;
745        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
746                mediumPriority, lowPriority);
747        assertEquals(0, highPriority.size());
748        assertEquals(0, mediumPriority.size());
749        assertEquals(1, lowPriority.size());
750        assertEquals("Recurring event with later start time expected", laterStartTime,
751                lowPriority.get(0).startMillis);
752    }
753
754    @SmallTest
755    public void testProcessQuery_recurringAllDayEvent() {
756        int eventId = 1;
757        long day1 = createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC);
758        long day2 = createTimeInMillis(0, 0, 0, 2, 5, 2012, Time.TIMEZONE_UTC);
759
760        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
761        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
762        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
763
764        AlertsTable at = new AlertsTable();
765        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day2, day2 + DateUtils.HOUR_IN_MILLIS * 24,
766                0);
767        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day1, day1 + DateUtils.HOUR_IN_MILLIS * 24,
768                0);
769
770        // Both events in the future: the earliest one should be chosen.
771        long currentTime = day1 - DateUtils.DAY_IN_MILLIS * 3;
772        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
773                mediumPriority, lowPriority);
774        assertEquals(0, lowPriority.size());
775        assertEquals(0, mediumPriority.size());
776        assertEquals(1, highPriority.size());
777        assertEquals("Recurring event with earlier start time expected", day1,
778                highPriority.get(0).startMillis);
779
780        // Increment time just past the earlier event (to 12:10am).  The earlier one should
781        // be chosen.
782        highPriority.clear();
783        currentTime = createTimeInMillis(0, 10, 0, 1, 5, 2012, Time.getCurrentTimezone());
784        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
785                mediumPriority, lowPriority);
786        assertEquals(0, lowPriority.size());
787        assertEquals(0, mediumPriority.size());
788        assertEquals(1, highPriority.size());
789        assertEquals("Recurring event with earlier start time expected", day1,
790                highPriority.get(0).startMillis);
791
792        // Increment time to 15 min past the earlier event: the later one should be chosen.
793        highPriority.clear();
794        currentTime = createTimeInMillis(0, 15, 0, 1, 5, 2012, Time.getCurrentTimezone());
795        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
796                mediumPriority, lowPriority);
797        assertEquals(0, lowPriority.size());
798        assertEquals(0, mediumPriority.size());
799        assertEquals(1, highPriority.size());
800        assertEquals("Recurring event with earlier start time expected", day2,
801                highPriority.get(0).startMillis);
802
803        // Both events in the past: the later one should be chosen (in the low priority bucket).
804        highPriority.clear();
805        currentTime = day2 + DateUtils.DAY_IN_MILLIS * 1;
806        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
807                mediumPriority, lowPriority);
808        assertEquals(0, highPriority.size());
809        assertEquals(0, mediumPriority.size());
810        assertEquals(1, lowPriority.size());
811        assertEquals("Recurring event with later start time expected", day2,
812                lowPriority.get(0).startMillis);
813    }
814
815    @SmallTest
816    public void testRedistributeBuckets_withinLimits() throws Exception {
817        int maxNotifications = 3;
818        ArrayList<NotificationInfo> threeItemList = new ArrayList<NotificationInfo>();
819        threeItemList.add(createNotificationInfo(5));
820        threeItemList.add(createNotificationInfo(4));
821        threeItemList.add(createNotificationInfo(3));
822
823        // Test when max notifications at high priority.
824        ArrayList<NotificationInfo> high = threeItemList;
825        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
826        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
827        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
828        assertEquals(3, high.size());
829        assertEquals(0, medium.size());
830        assertEquals(0, low.size());
831
832        // Test when max notifications at medium priority.
833        high = new ArrayList<NotificationInfo>();
834        medium = threeItemList;
835        low = new ArrayList<NotificationInfo>();
836        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
837        assertEquals(0, high.size());
838        assertEquals(3, medium.size());
839        assertEquals(0, low.size());
840
841        // Test when max notifications at high and medium priority
842        high = new ArrayList<NotificationInfo>(threeItemList);
843        medium = new ArrayList<NotificationInfo>();
844        medium.add(high.remove(1));
845        low = new ArrayList<NotificationInfo>();
846        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
847        assertEquals(2, high.size());
848        assertEquals(1, medium.size());
849        assertEquals(0, low.size());
850    }
851
852    @SmallTest
853    public void testRedistributeBuckets_tooManyHighPriority() throws Exception {
854        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
855        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
856        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
857        high.add(createNotificationInfo(5));
858        high.add(createNotificationInfo(4));
859        high.add(createNotificationInfo(3));
860        high.add(createNotificationInfo(2));
861        high.add(createNotificationInfo(1));
862
863        // Invoke the method under test.
864        int maxNotifications = 3;
865        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
866
867        // Verify some high priority were kicked out.
868        assertEquals(3, high.size());
869        assertEquals(3, high.get(0).eventId);
870        assertEquals(2, high.get(1).eventId);
871        assertEquals(1, high.get(2).eventId);
872
873        // Verify medium priority untouched.
874        assertEquals(0, medium.size());
875
876        // Verify the extras went to low priority.
877        assertEquals(2, low.size());
878        assertEquals(5, low.get(0).eventId);
879        assertEquals(4, low.get(1).eventId);
880    }
881
882    @SmallTest
883    public void testRedistributeBuckets_tooManyMediumPriority() throws Exception {
884        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
885        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
886        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
887        high.add(createNotificationInfo(5));
888        high.add(createNotificationInfo(4));
889        medium.add(createNotificationInfo(3));
890        medium.add(createNotificationInfo(2));
891        medium.add(createNotificationInfo(1));
892
893        // Invoke the method under test.
894        int maxNotifications = 3;
895        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
896
897        // Verify high priority untouched.
898        assertEquals(2, high.size());
899        assertEquals(5, high.get(0).eventId);
900        assertEquals(4, high.get(1).eventId);
901
902        // Verify some medium priority were kicked out (the ones near the end of the
903        // list).
904        assertEquals(1, medium.size());
905        assertEquals(3, medium.get(0).eventId);
906
907        // Verify the extras went to low priority.
908        assertEquals(2, low.size());
909        assertEquals(2, low.get(0).eventId);
910        assertEquals(1, low.get(1).eventId);
911    }
912
913    @SmallTest
914    public void testRedistributeBuckets_tooManyHighMediumPriority() throws Exception {
915        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
916        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
917        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
918        high.add(createNotificationInfo(8));
919        high.add(createNotificationInfo(7));
920        high.add(createNotificationInfo(6));
921        high.add(createNotificationInfo(5));
922        high.add(createNotificationInfo(4));
923        medium.add(createNotificationInfo(3));
924        medium.add(createNotificationInfo(2));
925        medium.add(createNotificationInfo(1));
926
927        // Invoke the method under test.
928        int maxNotifications = 3;
929        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
930
931        // Verify high priority.
932        assertEquals(3, high.size());
933        assertEquals(6, high.get(0).eventId);
934        assertEquals(5, high.get(1).eventId);
935        assertEquals(4, high.get(2).eventId);
936
937        // Verify some medium priority.
938        assertEquals(0, medium.size());
939
940        // Verify low priority.
941        assertEquals(5, low.size());
942        assertEquals(8, low.get(0).eventId);
943        assertEquals(7, low.get(1).eventId);
944        assertEquals(3, low.get(2).eventId);
945        assertEquals(2, low.get(3).eventId);
946        assertEquals(1, low.get(4).eventId);
947    }
948}
949