Home | History | Annotate | Download | only in alerts
      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 
     17 package com.android.calendar.alerts;
     18 
     19 import android.app.AlarmManager;
     20 import android.database.Cursor;
     21 import android.database.MatrixCursor;
     22 import android.net.Uri;
     23 import android.provider.CalendarContract;
     24 import android.provider.CalendarContract.Instances;
     25 import android.provider.CalendarContract.Reminders;
     26 import android.test.AndroidTestCase;
     27 import android.test.IsolatedContext;
     28 import android.test.mock.MockContentProvider;
     29 import android.test.mock.MockContentResolver;
     30 import android.test.suitebuilder.annotation.SmallTest;
     31 import android.text.format.DateUtils;
     32 import android.text.format.Time;
     33 import android.util.Log;
     34 
     35 import junit.framework.Assert;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.HashSet;
     40 
     41 @SmallTest
     42 public class AlarmSchedulerTest extends AndroidTestCase {
     43     private static final int BATCH_SIZE = 50;
     44     private MockProvider mMockProvider;
     45     private MockAlarmManager mMockAlarmManager;
     46     private IsolatedContext mIsolatedContext;
     47 
     48     /**
     49      * A helper class to mock query results from the test data.
     50      */
     51     private static class MockProvider extends MockContentProvider {
     52         private ArrayList<EventInfo> mEvents = new ArrayList<EventInfo>();
     53         private ArrayList<String> mExpectedRemindersQueries = new ArrayList<String>();
     54         private int mCurrentReminderQueryIndex = 0;
     55 
     56         /**
     57          * Contains info for a test event and its reminder.
     58          */
     59         private static class EventInfo {
     60             long mEventId;
     61             long mBegin;
     62             boolean mAllDay;
     63             int mReminderMinutes;
     64 
     65             public EventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) {
     66                 mEventId = eventId;
     67                 mAllDay = allDay;
     68                 mBegin = begin;
     69                 mReminderMinutes = reminderMinutes;
     70             }
     71 
     72         }
     73 
     74         /**
     75          * Adds event/reminder data for testing.  These will always be returned in the mocked
     76          * query result cursors.
     77          */
     78         void addEventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) {
     79             mEvents.add(new EventInfo(eventId, allDay, begin, reminderMinutes));
     80         }
     81 
     82         private MatrixCursor getInstancesCursor() {
     83             MatrixCursor instancesCursor = new MatrixCursor(AlarmScheduler.INSTANCES_PROJECTION);
     84             int i = 0;
     85             HashSet<Long> eventIds = new HashSet<Long>();
     86             for (EventInfo event : mEvents) {
     87                 if (!eventIds.contains(event.mEventId)) {
     88                     Object[] ca = {
     89                             event.mEventId,
     90                             event.mBegin,
     91                             event.mAllDay ? 1 : 0,
     92                     };
     93                     instancesCursor.addRow(ca);
     94                     eventIds.add(event.mEventId);
     95                 }
     96             }
     97             return instancesCursor;
     98         }
     99 
    100         private MatrixCursor getRemindersCursor() {
    101             MatrixCursor remindersCursor = new MatrixCursor(AlarmScheduler.REMINDERS_PROJECTION);
    102             int i = 0;
    103             for (EventInfo event : mEvents) {
    104                 Object[] ca = {
    105                         event.mEventId,
    106                         event.mReminderMinutes,
    107                         1,
    108                 };
    109                 remindersCursor.addRow(ca);
    110             }
    111             return remindersCursor;
    112         }
    113 
    114         @Override
    115         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    116                 String sortOrder) {
    117             if (uri.toString().startsWith(Instances.CONTENT_URI.toString())) {
    118                 return getInstancesCursor();
    119             } else if (Reminders.CONTENT_URI.equals(uri)) {
    120                 if (mExpectedRemindersQueries.size() > 0) {
    121                     if (mExpectedRemindersQueries.size() <= mCurrentReminderQueryIndex ||
    122                             !mExpectedRemindersQueries.get(mCurrentReminderQueryIndex).equals(
    123                                     selection)) {
    124                         String msg = "Reminders query not as expected.\n";
    125                         msg += "  Expected:";
    126                         msg += Arrays.deepToString(mExpectedRemindersQueries.toArray());
    127                         msg += "\n  Got in position " + mCurrentReminderQueryIndex + ": ";
    128                         msg += selection;
    129                         fail(msg);
    130                     }
    131                     mCurrentReminderQueryIndex++;
    132                 }
    133                 return getRemindersCursor();
    134             } else {
    135                 return super.query(uri, projection, selection, selectionArgs, sortOrder);
    136             }
    137         }
    138 
    139         /**
    140          * Optionally set up expectation for the reminders query selection.
    141          */
    142         public void addExpectedRemindersQuery(String expectedRemindersQuery) {
    143             this.mExpectedRemindersQueries.add(expectedRemindersQuery);
    144         }
    145     }
    146 
    147     /**
    148      * Expect an alarm for the specified time.
    149      */
    150     private void expectAlarmAt(long millis) {
    151         // AlarmScheduler adds a slight delay to the alarm so account for that here.
    152         mMockAlarmManager.expectAlarmTime(AlarmManager.RTC_WAKEUP,
    153                 millis + AlarmScheduler.ALARM_DELAY_MS);
    154     }
    155 
    156     @Override
    157     protected void setUp() throws Exception {
    158         super.setUp();
    159 
    160         mMockProvider = new MockProvider();
    161         mMockAlarmManager = new MockAlarmManager(mContext);
    162         MockContentResolver mockResolver = new MockContentResolver();
    163         mockResolver.addProvider(CalendarContract.AUTHORITY, mMockProvider);
    164         mIsolatedContext = new IsolatedContext(mockResolver, mContext);
    165     }
    166 
    167     public void testNoEvents() {
    168         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager,
    169                 BATCH_SIZE, System.currentTimeMillis());
    170         assertFalse(mMockAlarmManager.isAlarmSet());
    171     }
    172 
    173     public void testNonAllDayEvent() {
    174         // Set up mock test data.
    175         long currentMillis = System.currentTimeMillis();
    176         long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS;
    177         int reminderMin = 10;
    178         mMockProvider.addEventInfo(1, false, startMillis, reminderMin);
    179         expectAlarmAt(startMillis - reminderMin * DateUtils.MINUTE_IN_MILLIS);
    180 
    181         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    182         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    183                 currentMillis);
    184         assertTrue(mMockAlarmManager.isAlarmSet());
    185     }
    186 
    187     public void testAllDayEvent() {
    188         // Set up mock allday data.
    189         long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC);
    190         long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012,
    191                 Time.getCurrentTimezone());
    192         long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS;
    193         int reminderMin = 15;
    194         mMockProvider.addEventInfo(1, true, startMillisUtc, reminderMin);
    195         expectAlarmAt(startMillisLocal - reminderMin * DateUtils.MINUTE_IN_MILLIS);
    196 
    197         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    198         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    199                 currentMillis);
    200         assertTrue(mMockAlarmManager.isAlarmSet());
    201     }
    202 
    203     public void testAllDayAndNonAllDayEvents() {
    204         // Set up mock test data.
    205         long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC);
    206         long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012,
    207                 Time.getCurrentTimezone());
    208         long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS;
    209         mMockProvider.addEventInfo(1, true, startMillisUtc, 15);
    210         mMockProvider.addEventInfo(1, false, startMillisLocal, 10);
    211         expectAlarmAt(startMillisLocal - 15 * DateUtils.MINUTE_IN_MILLIS);
    212 
    213         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    214         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    215                 currentMillis);
    216         assertTrue(mMockAlarmManager.isAlarmSet());
    217     }
    218 
    219     public void testExpiredReminder() {
    220         // Set up mock test data.
    221         long currentMillis = System.currentTimeMillis();
    222         long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS;
    223         int reminderMin = 61;
    224         mMockProvider.addEventInfo(1, false, startMillis, reminderMin);
    225 
    226         // Invoke scheduleNextAlarm and verify no alarm was set.
    227         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    228                 currentMillis);
    229         assertFalse(mMockAlarmManager.isAlarmSet());
    230     }
    231 
    232     public void testAlarmMax() {
    233         // Set up mock test data for a reminder greater than 1 day in the future.
    234         // This will be maxed out to 1 day out.
    235         long currentMillis = System.currentTimeMillis();
    236         long startMillis = currentMillis + DateUtils.DAY_IN_MILLIS * 3;
    237         int reminderMin = (int) DateUtils.DAY_IN_MILLIS / (1000 * 60);
    238         mMockProvider.addEventInfo(1, false, startMillis, reminderMin);
    239         expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS);
    240 
    241         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    242         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    243                 currentMillis);
    244         assertTrue(mMockAlarmManager.isAlarmSet());
    245     }
    246 
    247     public void testMultipleEvents() {
    248         // Set up multiple events where a later event time has an earlier reminder time.
    249         long currentMillis = System.currentTimeMillis();
    250         mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 0);
    251         mMockProvider.addEventInfo(2, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 60, 45);
    252         mMockProvider.addEventInfo(3, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 30, 10);
    253 
    254         // Expect event 2's reminder.
    255         expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 15);
    256 
    257         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    258         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    259                 currentMillis);
    260         assertTrue(mMockAlarmManager.isAlarmSet());
    261     }
    262 
    263     public void testRecurringEvents() {
    264         long currentMillis = System.currentTimeMillis();
    265 
    266         // Event in 3 days, with a 2 day reminder
    267         mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS * 3,
    268                 (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */);
    269         // Event for tomorrow, with a 2 day reminder
    270         mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS,
    271                 (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */);
    272 
    273         // Expect the reminder for the top event because the reminder time for the bottom
    274         // one already passed.
    275         expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS);
    276 
    277         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    278         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    279                 currentMillis);
    280         assertTrue(mMockAlarmManager.isAlarmSet());
    281     }
    282 
    283     public void testMultipleRemindersForEvent() {
    284         // Set up mock test data.
    285         long currentMillis = System.currentTimeMillis();
    286         mMockProvider.addEventInfo(1,  false, currentMillis + DateUtils.DAY_IN_MILLIS, 10);
    287         mMockProvider.addEventInfo(1,  false, currentMillis + DateUtils.DAY_IN_MILLIS, 20);
    288         mMockProvider.addEventInfo(1,  false, currentMillis + DateUtils.DAY_IN_MILLIS, 15);
    289 
    290         // Expect earliest reminder.
    291         expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS - DateUtils.MINUTE_IN_MILLIS * 20);
    292 
    293         // Invoke scheduleNextAlarm and verify alarm was set at the expected time.
    294         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE,
    295                 currentMillis);
    296         assertTrue(mMockAlarmManager.isAlarmSet());
    297     }
    298 
    299     public void testLargeBatch() {
    300         // Add enough events to require several batches.
    301         long currentMillis = System.currentTimeMillis();
    302         int batchSize = 5;
    303         for (int i = 19; i > 0; i--) {
    304             mMockProvider.addEventInfo(i, false, currentMillis + DateUtils.HOUR_IN_MILLIS * i,
    305                     10);
    306         }
    307 
    308         // Set up expectations for the batch queries.
    309         expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 50);
    310         mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (19,18,17,16,15)");
    311         mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (14,13,12,11,10)");
    312         mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (9,8,7,6,5)");
    313         mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (4,3,2,1)");
    314 
    315         // Invoke scheduleNextAlarm and verify alarm and reminder query batches.
    316         AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, batchSize,
    317                 currentMillis);
    318     }
    319 }
    320