/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.calendar.alerts; import android.app.AlarmManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.provider.CalendarContract; import android.provider.CalendarContract.Instances; import android.provider.CalendarContract.Reminders; import android.test.AndroidTestCase; import android.test.IsolatedContext; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; import android.text.format.DateUtils; import android.text.format.Time; import android.util.Log; import junit.framework.Assert; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @SmallTest public class AlarmSchedulerTest extends AndroidTestCase { private static final int BATCH_SIZE = 50; private MockProvider mMockProvider; private MockAlarmManager mMockAlarmManager; private IsolatedContext mIsolatedContext; /** * A helper class to mock query results from the test data. */ private static class MockProvider extends MockContentProvider { private ArrayList<EventInfo> mEvents = new ArrayList<EventInfo>(); private ArrayList<String> mExpectedRemindersQueries = new ArrayList<String>(); private int mCurrentReminderQueryIndex = 0; /** * Contains info for a test event and its reminder. */ private static class EventInfo { long mEventId; long mBegin; boolean mAllDay; int mReminderMinutes; public EventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) { mEventId = eventId; mAllDay = allDay; mBegin = begin; mReminderMinutes = reminderMinutes; } } /** * Adds event/reminder data for testing. These will always be returned in the mocked * query result cursors. */ void addEventInfo(long eventId, boolean allDay, long begin, int reminderMinutes) { mEvents.add(new EventInfo(eventId, allDay, begin, reminderMinutes)); } private MatrixCursor getInstancesCursor() { MatrixCursor instancesCursor = new MatrixCursor(AlarmScheduler.INSTANCES_PROJECTION); int i = 0; HashSet<Long> eventIds = new HashSet<Long>(); for (EventInfo event : mEvents) { if (!eventIds.contains(event.mEventId)) { Object[] ca = { event.mEventId, event.mBegin, event.mAllDay ? 1 : 0, }; instancesCursor.addRow(ca); eventIds.add(event.mEventId); } } return instancesCursor; } private MatrixCursor getRemindersCursor() { MatrixCursor remindersCursor = new MatrixCursor(AlarmScheduler.REMINDERS_PROJECTION); int i = 0; for (EventInfo event : mEvents) { Object[] ca = { event.mEventId, event.mReminderMinutes, 1, }; remindersCursor.addRow(ca); } return remindersCursor; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (uri.toString().startsWith(Instances.CONTENT_URI.toString())) { return getInstancesCursor(); } else if (Reminders.CONTENT_URI.equals(uri)) { if (mExpectedRemindersQueries.size() > 0) { if (mExpectedRemindersQueries.size() <= mCurrentReminderQueryIndex || !mExpectedRemindersQueries.get(mCurrentReminderQueryIndex).equals( selection)) { String msg = "Reminders query not as expected.\n"; msg += " Expected:"; msg += Arrays.deepToString(mExpectedRemindersQueries.toArray()); msg += "\n Got in position " + mCurrentReminderQueryIndex + ": "; msg += selection; fail(msg); } mCurrentReminderQueryIndex++; } return getRemindersCursor(); } else { return super.query(uri, projection, selection, selectionArgs, sortOrder); } } /** * Optionally set up expectation for the reminders query selection. */ public void addExpectedRemindersQuery(String expectedRemindersQuery) { this.mExpectedRemindersQueries.add(expectedRemindersQuery); } } /** * Expect an alarm for the specified time. */ private void expectAlarmAt(long millis) { // AlarmScheduler adds a slight delay to the alarm so account for that here. mMockAlarmManager.expectAlarmTime(AlarmManager.RTC_WAKEUP, millis + AlarmScheduler.ALARM_DELAY_MS); } @Override protected void setUp() throws Exception { super.setUp(); mMockProvider = new MockProvider(); mMockAlarmManager = new MockAlarmManager(mContext); MockContentResolver mockResolver = new MockContentResolver(); mockResolver.addProvider(CalendarContract.AUTHORITY, mMockProvider); mIsolatedContext = new IsolatedContext(mockResolver, mContext); } public void testNoEvents() { AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, System.currentTimeMillis()); assertFalse(mMockAlarmManager.isAlarmSet()); } public void testNonAllDayEvent() { // Set up mock test data. long currentMillis = System.currentTimeMillis(); long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS; int reminderMin = 10; mMockProvider.addEventInfo(1, false, startMillis, reminderMin); expectAlarmAt(startMillis - reminderMin * DateUtils.MINUTE_IN_MILLIS); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testAllDayEvent() { // Set up mock allday data. long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC); long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.getCurrentTimezone()); long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS; int reminderMin = 15; mMockProvider.addEventInfo(1, true, startMillisUtc, reminderMin); expectAlarmAt(startMillisLocal - reminderMin * DateUtils.MINUTE_IN_MILLIS); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testAllDayAndNonAllDayEvents() { // Set up mock test data. long startMillisUtc = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.TIMEZONE_UTC); long startMillisLocal = Utils.createTimeInMillis(0, 0, 0, 1, 5, 2012, Time.getCurrentTimezone()); long currentMillis = startMillisLocal - DateUtils.DAY_IN_MILLIS; mMockProvider.addEventInfo(1, true, startMillisUtc, 15); mMockProvider.addEventInfo(1, false, startMillisLocal, 10); expectAlarmAt(startMillisLocal - 15 * DateUtils.MINUTE_IN_MILLIS); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testExpiredReminder() { // Set up mock test data. long currentMillis = System.currentTimeMillis(); long startMillis = currentMillis + DateUtils.HOUR_IN_MILLIS; int reminderMin = 61; mMockProvider.addEventInfo(1, false, startMillis, reminderMin); // Invoke scheduleNextAlarm and verify no alarm was set. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertFalse(mMockAlarmManager.isAlarmSet()); } public void testAlarmMax() { // Set up mock test data for a reminder greater than 1 day in the future. // This will be maxed out to 1 day out. long currentMillis = System.currentTimeMillis(); long startMillis = currentMillis + DateUtils.DAY_IN_MILLIS * 3; int reminderMin = (int) DateUtils.DAY_IN_MILLIS / (1000 * 60); mMockProvider.addEventInfo(1, false, startMillis, reminderMin); expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testMultipleEvents() { // Set up multiple events where a later event time has an earlier reminder time. long currentMillis = System.currentTimeMillis(); mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 0); mMockProvider.addEventInfo(2, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 60, 45); mMockProvider.addEventInfo(3, false, currentMillis + DateUtils.MINUTE_IN_MILLIS * 30, 10); // Expect event 2's reminder. expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 15); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testRecurringEvents() { long currentMillis = System.currentTimeMillis(); // Event in 3 days, with a 2 day reminder mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS * 3, (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */); // Event for tomorrow, with a 2 day reminder mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, (int) DateUtils.DAY_IN_MILLIS * 2 / (1000 * 60) /* 2 day reminder */); // Expect the reminder for the top event because the reminder time for the bottom // one already passed. expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testMultipleRemindersForEvent() { // Set up mock test data. long currentMillis = System.currentTimeMillis(); mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 10); mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 20); mMockProvider.addEventInfo(1, false, currentMillis + DateUtils.DAY_IN_MILLIS, 15); // Expect earliest reminder. expectAlarmAt(currentMillis + DateUtils.DAY_IN_MILLIS - DateUtils.MINUTE_IN_MILLIS * 20); // Invoke scheduleNextAlarm and verify alarm was set at the expected time. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, BATCH_SIZE, currentMillis); assertTrue(mMockAlarmManager.isAlarmSet()); } public void testLargeBatch() { // Add enough events to require several batches. long currentMillis = System.currentTimeMillis(); int batchSize = 5; for (int i = 19; i > 0; i--) { mMockProvider.addEventInfo(i, false, currentMillis + DateUtils.HOUR_IN_MILLIS * i, 10); } // Set up expectations for the batch queries. expectAlarmAt(currentMillis + DateUtils.MINUTE_IN_MILLIS * 50); mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (19,18,17,16,15)"); mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (14,13,12,11,10)"); mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (9,8,7,6,5)"); mMockProvider.addExpectedRemindersQuery("method=1 AND event_id IN (4,3,2,1)"); // Invoke scheduleNextAlarm and verify alarm and reminder query batches. AlarmScheduler.scheduleNextAlarm(mIsolatedContext, mMockAlarmManager, batchSize, currentMillis); } }