AlertServiceTest.java revision 948c590ced6854d2fbe9dc765db4ae8d63646664
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 extends 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 notify(int id, NotificationWrapper nw) {
329            if (mDone == null) {
330                mDone = new boolean[mNotifications.length];
331            }
332            assertTrue("id out of bound: " + id, 0 <= id);
333            assertTrue("id out of bound: " + id, id < mDone.length);
334            assertFalse("id already used", mDone[id]);
335            mDone[id] = true;
336
337            assertNotNull("Unexpected notify for id " + id, mNotifications[id]);
338
339            verifyNotification(id, nw);
340        }
341    }
342
343    // TODO
344    // Catch updates of new state, notify time, and received time
345    // Test ringer, vibrate,
346    // Test digest notifications
347    // Test intents, action email
348    // Catch alarmmgr calls
349
350    @Smoke
351    @SmallTest
352    public void testNoAlerts() {
353        MockSharedPreferences prefs = new MockSharedPreferences();
354        AlertsTable at = new AlertsTable();
355        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts,
356                AlertService.MAX_NOTIFICATIONS);
357
358        // Test no alert
359        long currentTime = 1000000;
360        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
361                AlertService.MAX_NOTIFICATIONS);
362        ntm.validateNotificationsAndReset();
363    }
364
365    @Smoke
366    @SmallTest
367    public void testSingleAlert() {
368        MockSharedPreferences prefs = new MockSharedPreferences();
369        AlertsTable at = new AlertsTable();
370        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts,
371                AlertService.MAX_NOTIFICATIONS);
372
373        int id = at.addAlertRow(100, SCHEDULED, ACCEPTED, 0 /* all day */, 1300000, 2300000, 0);
374
375        // Test one up coming alert
376        long currentTime = 1000000;
377        ntm.expectTestNotification(1, id, PRIORITY_HIGH);
378
379        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
380                AlertService.MAX_NOTIFICATIONS);
381        ntm.validateNotificationsAndReset(); // This wipes out notification
382                                             // tests added so far
383
384        // Test half way into an event
385        currentTime = 2300000;
386        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_DEFAULT);
387
388        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
389                AlertService.MAX_NOTIFICATIONS);
390        ntm.validateNotificationsAndReset();
391
392        // Test event ended
393        currentTime = 4300000;
394        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, id, PRIORITY_MIN);
395
396        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
397                AlertService.MAX_NOTIFICATIONS);
398        ntm.validateNotificationsAndReset();
399    }
400
401    @SmallTest
402    public void testMultipleAlerts() {
403        int maxNotifications = 10;
404        MockSharedPreferences prefs = new MockSharedPreferences();
405        AlertsTable at = new AlertsTable();
406        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
407
408        // Current time - 5:00
409        long currentTime = createTimeInMillis(5, 0);
410
411        // Set up future alerts.  The real query implementation sorts by descending start
412        // time so simulate that here with our order of adds to AlertsTable.
413        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
414                createTimeInMillis(10, 0), 0);
415        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
416                createTimeInMillis(9, 0), 0);
417        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
418                createTimeInMillis(8, 0), 0);
419
420        // Set up concurrent alerts (that started recently).
421        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
422                createTimeInMillis(5, 40), 0);
423        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
424                createTimeInMillis(7, 30), 0);
425        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
426                createTimeInMillis(4, 50), 0);
427
428        // Set up past alerts.
429        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
430                createTimeInMillis(4, 0), 0);
431        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
432                createTimeInMillis(3, 0), 0);
433        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
434                createTimeInMillis(2, 0), 0);
435
436        // Check posted notifications.  The order listed here is the order simulates the
437        // order in the real notification bar (last one posted appears on top), so these
438        // should be lowest start time on top.
439        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
440        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
441        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
442        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
443        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
444        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
445        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
446                new int[] {id3, id2, id1}, PRIORITY_MIN);
447        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
448                maxNotifications);
449        ntm.validateNotificationsAndReset();
450
451        // Increase time by 15 minutes to check that some concurrent events dropped
452        // to the low priority bucket.
453        currentTime = createTimeInMillis(5, 15);
454        ntm.expectTestNotification(4, id5, PRIORITY_HIGH); // concurrent
455        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
456        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
457        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
458        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
459                new int[] {id6, id4, id3, id2, id1}, PRIORITY_MIN);
460        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
461                maxNotifications);
462        ntm.validateNotificationsAndReset();
463
464        // Increase time so some of the previously future ones change state.
465        currentTime = createTimeInMillis(8, 15);
466        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
467        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
468                new int[] {id8, id7, id6, id5, id4, id3, id2, id1}, PRIORITY_MIN);
469        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
470                maxNotifications);
471        ntm.validateNotificationsAndReset();
472    }
473
474    @SmallTest
475    public void testMultipleAlerts_max() {
476        MockSharedPreferences prefs = new MockSharedPreferences();
477        AlertsTable at = new AlertsTable();
478
479        // Current time - 5:00
480        long currentTime = createTimeInMillis(5, 0);
481
482        // Set up future alerts.  The real query implementation sorts by descending start
483        // time so simulate that here with our order of adds to AlertsTable.
484        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
485                createTimeInMillis(10, 0), 0);
486        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
487                createTimeInMillis(9, 0), 0);
488        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
489                createTimeInMillis(8, 0), 0);
490
491        // Set up concurrent alerts (that started recently).
492        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
493                createTimeInMillis(5, 40), 0);
494        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
495                createTimeInMillis(7, 30), 0);
496        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
497                createTimeInMillis(4, 50), 0);
498
499        // Set up past alerts.
500        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
501                createTimeInMillis(4, 0), 0);
502        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
503                createTimeInMillis(3, 0), 0);
504        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
505                createTimeInMillis(2, 0), 0);
506
507        // Test when # alerts = max.
508        int maxNotifications = 6;
509        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
510        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
511        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
512        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
513        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
514        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
515        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
516        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
517                new int[] {id3, id2, id1}, PRIORITY_MIN);
518        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
519                maxNotifications);
520        ntm.validateNotificationsAndReset();
521
522        // Test when # alerts > max.
523        maxNotifications = 4;
524        ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
525        ntm.expectTestNotification(4, id4, PRIORITY_HIGH); // concurrent
526        ntm.expectTestNotification(3, id5, PRIORITY_HIGH); // concurrent
527        ntm.expectTestNotification(2, id6, PRIORITY_HIGH); // concurrent
528        ntm.expectTestNotification(1, id7, PRIORITY_HIGH); // future
529        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
530                new int[] {id9, id8, id3, id2, id1}, PRIORITY_MIN);
531        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
532                maxNotifications);
533        ntm.validateNotificationsAndReset();
534    }
535
536    /**
537     * Test that the SharedPreferences are only fetched once for each setting.
538     */
539    @SmallTest
540    public void testMultipleAlerts_sharedPreferences() {
541        int maxNotifications = 10;
542        MockSharedPreferences prefs = new MockSharedPreferences(true /* strict mode */);
543        AlertsTable at = new AlertsTable();
544        NotificationTestManager ntm = new NotificationTestManager(at.mAlerts, maxNotifications);
545
546        // Current time - 5:00
547        long currentTime = createTimeInMillis(5, 0);
548
549        // Set up future alerts.  The real query implementation sorts by descending start
550        // time so simulate that here with our order of adds to AlertsTable.
551        int id9 = at.addAlertRow(9, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
552                createTimeInMillis(10, 0), 0);
553        int id8 = at.addAlertRow(8, SCHEDULED, ACCEPTED, 0, createTimeInMillis(8, 0),
554                createTimeInMillis(9, 0), 0);
555        int id7 = at.addAlertRow(7, SCHEDULED, ACCEPTED, 0, createTimeInMillis(7, 0),
556                createTimeInMillis(8, 0), 0);
557
558        // Set up concurrent alerts (that started recently).
559        int id6 = at.addAlertRow(6, SCHEDULED, ACCEPTED, 0, createTimeInMillis(5, 0),
560                createTimeInMillis(5, 40), 0);
561        int id5 = at.addAlertRow(5, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 55),
562                createTimeInMillis(7, 30), 0);
563        int id4 = at.addAlertRow(4, SCHEDULED, ACCEPTED, 0, createTimeInMillis(4, 50),
564                createTimeInMillis(4, 50), 0);
565
566        // Set up past alerts.
567        int id3 = at.addAlertRow(3, SCHEDULED, ACCEPTED, 0, createTimeInMillis(3, 0),
568                createTimeInMillis(4, 0), 0);
569        int id2 = at.addAlertRow(2, SCHEDULED, ACCEPTED, 0, createTimeInMillis(2, 0),
570                createTimeInMillis(3, 0), 0);
571        int id1 = at.addAlertRow(1, SCHEDULED, ACCEPTED, 0, createTimeInMillis(1, 0),
572                createTimeInMillis(2, 0), 0);
573
574        // Expected notifications.
575        ntm.expectTestNotification(6, id4, PRIORITY_HIGH); // concurrent
576        ntm.expectTestNotification(5, id5, PRIORITY_HIGH); // concurrent
577        ntm.expectTestNotification(4, id6, PRIORITY_HIGH); // concurrent
578        ntm.expectTestNotification(3, id7, PRIORITY_HIGH); // future
579        ntm.expectTestNotification(2, id8, PRIORITY_HIGH); // future
580        ntm.expectTestNotification(1, id9, PRIORITY_HIGH); // future
581        ntm.expectTestNotification(AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID,
582                new int[] {id3, id2, id1}, PRIORITY_MIN);
583
584        // If this does not result in a failure (MockSharedPreferences fails for duplicate
585        // queries), then test passes.
586        AlertService.generateAlerts(mContext, ntm, prefs, at.getAlertCursor(), currentTime,
587                maxNotifications);
588        ntm.validateNotificationsAndReset();
589    }
590
591    private NotificationInfo createNotificationInfo(long eventId) {
592        return new NotificationInfo("eventName", "location", "description", 100L, 200L, eventId,
593                false, false);
594    }
595
596    private static long createTimeInMillis(int hour, int minute) {
597        return createTimeInMillis(0 /* second */, minute, hour, 1 /* day */, 1 /* month */,
598                2012 /* year */, Time.getCurrentTimezone());
599    }
600
601    private static long createTimeInMillis(int second, int minute, int hour, int monthDay,
602            int month, int year, String timezone) {
603        Time t = new Time(timezone);
604        t.set(second, minute, hour, monthDay, month, year);
605        t.normalize(false);
606        return t.toMillis(false);
607    }
608
609    @SmallTest
610    public void testProcessQuery_skipDeclinedDismissed() {
611        int declinedEventId = 1;
612        int dismissedEventId = 2;
613        int acceptedEventId = 3;
614        long acceptedStartTime = createTimeInMillis(10, 0);
615        long acceptedEndTime = createTimeInMillis(10, 30);
616
617        AlertsTable at = new AlertsTable();
618        at.addAlertRow(declinedEventId, SCHEDULED, DECLINED, 0, createTimeInMillis(9, 0),
619                createTimeInMillis(10, 0), 0);
620        at.addAlertRow(dismissedEventId, SCHEDULED, DISMISSED, 0, createTimeInMillis(9, 30),
621                createTimeInMillis(11, 0), 0);
622        at.addAlertRow(acceptedEventId, SCHEDULED, ACCEPTED, 1, acceptedStartTime, acceptedEndTime,
623                0);
624
625        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
626        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
627        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
628        long currentTime = createTimeInMillis(5, 0);
629        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
630                mediumPriority, lowPriority);
631
632        assertEquals(0, lowPriority.size());
633        assertEquals(0, mediumPriority.size());
634        assertEquals(1, highPriority.size());
635        assertEquals(acceptedEventId, highPriority.get(0).eventId);
636        assertEquals(acceptedStartTime, highPriority.get(0).startMillis);
637        assertEquals(acceptedEndTime, highPriority.get(0).endMillis);
638        assertTrue(highPriority.get(0).allDay);
639    }
640
641    @SmallTest
642    public void testProcessQuery_newAlert() {
643        int scheduledAlertEventId = 1;
644        int firedAlertEventId = 2;
645
646        AlertsTable at = new AlertsTable();
647        at.addAlertRow(scheduledAlertEventId, SCHEDULED, ACCEPTED, 0, createTimeInMillis(9, 0),
648                createTimeInMillis(10, 0), 0);
649        at.addAlertRow(firedAlertEventId, FIRED, ACCEPTED, 0, createTimeInMillis(10, 0),
650                createTimeInMillis(10, 30), 0);
651
652        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
653        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
654        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
655        long currentTime = createTimeInMillis(5, 0);
656        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
657                mediumPriority, lowPriority);
658
659        assertEquals(0, lowPriority.size());
660        assertEquals(0, mediumPriority.size());
661        assertEquals(2, highPriority.size());
662        assertEquals(scheduledAlertEventId, highPriority.get(0).eventId);
663        assertTrue("newAlert should be ON for scheduled alerts", highPriority.get(0).newAlert);
664        assertEquals(firedAlertEventId, highPriority.get(1).eventId);
665        assertFalse("newAlert should be OFF for fired alerts", highPriority.get(1).newAlert);
666    }
667
668    @SmallTest
669    public void testProcessQuery_recurringEvent() {
670        int eventId = 1;
671        long earlierStartTime = createTimeInMillis(10, 0);
672        long laterStartTime = createTimeInMillis(11, 0);
673
674        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
675        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
676        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
677
678        AlertsTable at = new AlertsTable();
679        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 0, laterStartTime,
680                laterStartTime + DateUtils.HOUR_IN_MILLIS, 0);
681        at.addAlertRow(eventId, FIRED, ACCEPTED, 0, earlierStartTime,
682                earlierStartTime + DateUtils.HOUR_IN_MILLIS, 0);
683
684        // Both events in the future: the earliest one should be chosen.
685        long currentTime = earlierStartTime - DateUtils.DAY_IN_MILLIS * 5;
686        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
687                mediumPriority, lowPriority);
688        assertEquals(0, lowPriority.size());
689        assertEquals(0, mediumPriority.size());
690        assertEquals(1, highPriority.size());
691        assertEquals("Recurring event with earlier start time expected", earlierStartTime,
692                highPriority.get(0).startMillis);
693
694        // Increment time just past the earlier event: the earlier one should be chosen.
695        highPriority.clear();
696        currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 10;
697        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
698                mediumPriority, lowPriority);
699        assertEquals(0, lowPriority.size());
700        assertEquals(0, mediumPriority.size());
701        assertEquals(1, highPriority.size());
702        assertEquals("Recurring event with earlier start time expected", earlierStartTime,
703                highPriority.get(0).startMillis);
704
705        // Increment time to 15 min past the earlier event: the later one should be chosen.
706        highPriority.clear();
707        currentTime = earlierStartTime + DateUtils.MINUTE_IN_MILLIS * 15;
708        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
709                mediumPriority, lowPriority);
710        assertEquals(0, lowPriority.size());
711        assertEquals(0, mediumPriority.size());
712        assertEquals(1, highPriority.size());
713        assertEquals("Recurring event with later start time expected", laterStartTime,
714                highPriority.get(0).startMillis);
715
716        // Both events in the past: the later one should be chosen (in the low priority bucket).
717        highPriority.clear();
718        currentTime = laterStartTime + DateUtils.DAY_IN_MILLIS * 5;
719        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
720                mediumPriority, lowPriority);
721        assertEquals(0, highPriority.size());
722        assertEquals(0, mediumPriority.size());
723        assertEquals(1, lowPriority.size());
724        assertEquals("Recurring event with later start time expected", laterStartTime,
725                lowPriority.get(0).startMillis);
726    }
727
728    @SmallTest
729    public void testProcessQuery_recurringAllDayEvent() {
730        int eventId = 1;
731        long day1 = createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC);
732        long day2 = createTimeInMillis(0, 0, 0, 2, 5, 2012, Time.TIMEZONE_UTC);
733
734        ArrayList<NotificationInfo> highPriority = new ArrayList<NotificationInfo>();
735        ArrayList<NotificationInfo> mediumPriority = new ArrayList<NotificationInfo>();
736        ArrayList<NotificationInfo> lowPriority = new ArrayList<NotificationInfo>();
737
738        AlertsTable at = new AlertsTable();
739        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day2, day2 + DateUtils.HOUR_IN_MILLIS * 24,
740                0);
741        at.addAlertRow(eventId, SCHEDULED, ACCEPTED, 1, day1, day1 + DateUtils.HOUR_IN_MILLIS * 24,
742                0);
743
744        // Both events in the future: the earliest one should be chosen.
745        long currentTime = day1 - DateUtils.DAY_IN_MILLIS * 3;
746        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
747                mediumPriority, lowPriority);
748        assertEquals(0, lowPriority.size());
749        assertEquals(0, mediumPriority.size());
750        assertEquals(1, highPriority.size());
751        assertEquals("Recurring event with earlier start time expected", day1,
752                highPriority.get(0).startMillis);
753
754        // Increment time just past the earlier event (to 12:10am).  The earlier one should
755        // be chosen.
756        highPriority.clear();
757        currentTime = createTimeInMillis(0, 10, 0, 1, 5, 2012, Time.getCurrentTimezone());
758        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
759                mediumPriority, lowPriority);
760        assertEquals(0, lowPriority.size());
761        assertEquals(0, mediumPriority.size());
762        assertEquals(1, highPriority.size());
763        assertEquals("Recurring event with earlier start time expected", day1,
764                highPriority.get(0).startMillis);
765
766        // Increment time to 15 min past the earlier event: the later one should be chosen.
767        highPriority.clear();
768        currentTime = createTimeInMillis(0, 15, 0, 1, 5, 2012, Time.getCurrentTimezone());
769        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
770                mediumPriority, lowPriority);
771        assertEquals(0, lowPriority.size());
772        assertEquals(0, mediumPriority.size());
773        assertEquals(1, highPriority.size());
774        assertEquals("Recurring event with earlier start time expected", day2,
775                highPriority.get(0).startMillis);
776
777        // Both events in the past: the later one should be chosen (in the low priority bucket).
778        highPriority.clear();
779        currentTime = day2 + DateUtils.DAY_IN_MILLIS * 1;
780        AlertService.processQuery(at.getAlertCursor(), mContext, currentTime, highPriority,
781                mediumPriority, lowPriority);
782        assertEquals(0, highPriority.size());
783        assertEquals(0, mediumPriority.size());
784        assertEquals(1, lowPriority.size());
785        assertEquals("Recurring event with later start time expected", day2,
786                lowPriority.get(0).startMillis);
787    }
788
789    @SmallTest
790    public void testRedistributeBuckets_withinLimits() throws Exception {
791        int maxNotifications = 3;
792        ArrayList<NotificationInfo> threeItemList = new ArrayList<NotificationInfo>();
793        threeItemList.add(createNotificationInfo(5));
794        threeItemList.add(createNotificationInfo(4));
795        threeItemList.add(createNotificationInfo(3));
796
797        // Test when max notifications at high priority.
798        ArrayList<NotificationInfo> high = threeItemList;
799        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
800        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
801        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
802        assertEquals(3, high.size());
803        assertEquals(0, medium.size());
804        assertEquals(0, low.size());
805
806        // Test when max notifications at medium priority.
807        high = new ArrayList<NotificationInfo>();
808        medium = threeItemList;
809        low = new ArrayList<NotificationInfo>();
810        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
811        assertEquals(0, high.size());
812        assertEquals(3, medium.size());
813        assertEquals(0, low.size());
814
815        // Test when max notifications at high and medium priority
816        high = new ArrayList<NotificationInfo>(threeItemList);
817        medium = new ArrayList<NotificationInfo>();
818        medium.add(high.remove(1));
819        low = new ArrayList<NotificationInfo>();
820        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
821        assertEquals(2, high.size());
822        assertEquals(1, medium.size());
823        assertEquals(0, low.size());
824    }
825
826    @SmallTest
827    public void testRedistributeBuckets_tooManyHighPriority() throws Exception {
828        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
829        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
830        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
831        high.add(createNotificationInfo(5));
832        high.add(createNotificationInfo(4));
833        high.add(createNotificationInfo(3));
834        high.add(createNotificationInfo(2));
835        high.add(createNotificationInfo(1));
836
837        // Invoke the method under test.
838        int maxNotifications = 3;
839        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
840
841        // Verify some high priority were kicked out.
842        assertEquals(3, high.size());
843        assertEquals(3, high.get(0).eventId);
844        assertEquals(2, high.get(1).eventId);
845        assertEquals(1, high.get(2).eventId);
846
847        // Verify medium priority untouched.
848        assertEquals(0, medium.size());
849
850        // Verify the extras went to low priority.
851        assertEquals(2, low.size());
852        assertEquals(5, low.get(0).eventId);
853        assertEquals(4, low.get(1).eventId);
854    }
855
856    @SmallTest
857    public void testRedistributeBuckets_tooManyMediumPriority() throws Exception {
858        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
859        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
860        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
861        high.add(createNotificationInfo(5));
862        high.add(createNotificationInfo(4));
863        medium.add(createNotificationInfo(3));
864        medium.add(createNotificationInfo(2));
865        medium.add(createNotificationInfo(1));
866
867        // Invoke the method under test.
868        int maxNotifications = 3;
869        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
870
871        // Verify high priority untouched.
872        assertEquals(2, high.size());
873        assertEquals(5, high.get(0).eventId);
874        assertEquals(4, high.get(1).eventId);
875
876        // Verify some medium priority were kicked out (the ones near the end of the
877        // list).
878        assertEquals(1, medium.size());
879        assertEquals(3, medium.get(0).eventId);
880
881        // Verify the extras went to low priority.
882        assertEquals(2, low.size());
883        assertEquals(2, low.get(0).eventId);
884        assertEquals(1, low.get(1).eventId);
885    }
886
887    @SmallTest
888    public void testRedistributeBuckets_tooManyHighMediumPriority() throws Exception {
889        ArrayList<NotificationInfo> high = new ArrayList<NotificationInfo>();
890        ArrayList<NotificationInfo> medium = new ArrayList<NotificationInfo>();
891        ArrayList<NotificationInfo> low = new ArrayList<NotificationInfo>();
892        high.add(createNotificationInfo(8));
893        high.add(createNotificationInfo(7));
894        high.add(createNotificationInfo(6));
895        high.add(createNotificationInfo(5));
896        high.add(createNotificationInfo(4));
897        medium.add(createNotificationInfo(3));
898        medium.add(createNotificationInfo(2));
899        medium.add(createNotificationInfo(1));
900
901        // Invoke the method under test.
902        int maxNotifications = 3;
903        AlertService.redistributeBuckets(high, medium, low, maxNotifications);
904
905        // Verify high priority.
906        assertEquals(3, high.size());
907        assertEquals(6, high.get(0).eventId);
908        assertEquals(5, high.get(1).eventId);
909        assertEquals(4, high.get(2).eventId);
910
911        // Verify some medium priority.
912        assertEquals(0, medium.size());
913
914        // Verify low priority.
915        assertEquals(5, low.size());
916        assertEquals(8, low.get(0).eventId);
917        assertEquals(7, low.get(1).eventId);
918        assertEquals(3, low.get(2).eventId);
919        assertEquals(2, low.get(3).eventId);
920        assertEquals(1, low.get(4).eventId);
921    }
922}
923