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