CalendarProvider2Test.java revision 3443e3ebeaa39e8415b43e7cf3b218caee554e9b
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.providers.calendar; 18 19import android.database.sqlite.SQLiteOpenHelper; 20import com.android.common.ArrayListCursor; 21 22import android.content.*; 23import android.database.Cursor; 24import android.database.sqlite.SQLiteDatabase; 25import android.net.Uri; 26import android.text.format.DateUtils; 27import android.text.format.Time; 28import android.provider.Calendar; 29import android.provider.Calendar.Calendars; 30import android.provider.Calendar.Events; 31import android.provider.Calendar.EventsEntity; 32import android.provider.Calendar.Instances; 33import android.test.ProviderTestCase2; 34import android.test.mock.MockContentResolver; 35import android.test.suitebuilder.annotation.Suppress; 36import android.util.Log; 37 38import java.util.ArrayList; 39import java.util.HashMap; 40import java.util.Map; 41import java.util.TimeZone; 42 43/** 44 * Runs various tests on an isolated Calendar provider with its own database. 45 * 46 * You can run the tests with the following command line: 47 * 48 * adb shell am instrument 49 * -e debug false 50 * -w 51 * -e class com.android.providers.calendar.CalendarProvider2Test 52 * com.android.providers.calendar.tests/android.test.InstrumentationTestRunner 53 */ 54// flaky test, add back to LargeTest when fixed - bug 2395696 55// @LargeTest 56public class CalendarProvider2Test extends ProviderTestCase2<CalendarProvider2ForTesting> { 57 static final String TAG = "calendar"; 58 59 private SQLiteDatabase mDb; 60 private MetaData mMetaData; 61 private Context mContext; 62 private MockContentResolver mResolver; 63 private Uri mEventsUri = Events.CONTENT_URI; 64 private int mCalendarId; 65 66 protected boolean mWipe = false; 67 protected boolean mForceDtend = false; 68 69 // We need a unique id to put in the _sync_id field so that we can create 70 // recurrence exceptions that refer to recurring events. 71 private int mGlobalSyncId = 1000; 72 private static final String CALENDAR_URL = 73 "http://www.google.com/calendar/feeds/joe%40joe.com/private/full"; 74 75 private static final String TIME_ZONE_AMERICA_ANCHORAGE = "America/Anchorage"; 76 private static final String TIME_ZONE_AMERICA_LOS_ANGELES = "America/Los_Angeles"; 77 private static final String DEFAULT_TIMEZONE = TIME_ZONE_AMERICA_LOS_ANGELES; 78 79 private static final String MOCK_TIME_ZONE_DATABASE_VERSION = "2010a"; 80 81 /** 82 * KeyValue is a simple class that stores a pair of strings representing 83 * a (key, value) pair. This is used for updating events. 84 */ 85 private class KeyValue { 86 String key; 87 String value; 88 89 public KeyValue(String key, String value) { 90 this.key = key; 91 this.value = value; 92 } 93 } 94 95 /** 96 * A generic command interface. This is used to support a sequence of 97 * commands that can create events, delete or update events, and then 98 * check that the state of the database is as expected. 99 */ 100 private interface Command { 101 public void execute(); 102 } 103 104 /** 105 * This is used to insert a new event into the database. The event is 106 * specified by its name (or "title"). All of the event fields (the 107 * start and end time, whether it is an all-day event, and so on) are 108 * stored in a separate table (the "mEvents" table). 109 */ 110 private class Insert implements Command { 111 EventInfo eventInfo; 112 113 public Insert(String eventName) { 114 eventInfo = findEvent(eventName); 115 } 116 117 public void execute() { 118 Log.i(TAG, "insert " + eventInfo.mTitle); 119 insertEvent(mCalendarId, eventInfo); 120 } 121 } 122 123 /** 124 * This is used to delete an event, specified by the event name. 125 */ 126 private class Delete implements Command { 127 String eventName; 128 int expected; 129 130 public Delete(String eventName, int expected) { 131 this.eventName = eventName; 132 this.expected = expected; 133 } 134 135 public void execute() { 136 Log.i(TAG, "delete " + eventName); 137 int rows = deleteMatchingEvents(eventName); 138 assertEquals(expected, rows); 139 } 140 } 141 142 /** 143 * This is used to update an event. The values to update are specified 144 * with an array of (key, value) pairs. Both the key and value are 145 * specified as strings. Event fields that are not really strings (such 146 * as DTSTART which is a long) should be converted to the appropriate type 147 * but that isn't supported yet. When needed, that can be added here 148 * by checking for specific keys and converting the associated values. 149 */ 150 private class Update implements Command { 151 String eventName; 152 KeyValue[] pairs; 153 154 public Update(String eventName, KeyValue[] pairs) { 155 this.eventName = eventName; 156 this.pairs = pairs; 157 } 158 159 public void execute() { 160 Log.i(TAG, "update " + eventName); 161 if (mWipe) { 162 // Wipe instance table so it will be regenerated 163 mMetaData.clearInstanceRange(); 164 } 165 ContentValues map = new ContentValues(); 166 for (KeyValue pair : pairs) { 167 String value = pair.value; 168 if (Calendar.EventsColumns.STATUS.equals(pair.key)) { 169 // Do type conversion for STATUS 170 map.put(pair.key, Integer.parseInt(value)); 171 } else { 172 map.put(pair.key, value); 173 } 174 } 175 updateMatchingEvents(eventName, map); 176 } 177 } 178 179 /** 180 * This command queries the number of events and compares it to the given 181 * expected value. 182 */ 183 private class QueryNumEvents implements Command { 184 int expected; 185 186 public QueryNumEvents(int expected) { 187 this.expected = expected; 188 } 189 190 public void execute() { 191 Cursor cursor = mResolver.query(mEventsUri, null, null, null, null); 192 assertEquals(expected, cursor.getCount()); 193 cursor.close(); 194 } 195 } 196 197 198 /** 199 * This command dumps the list of events to the log for debugging. 200 */ 201 private class DumpEvents implements Command { 202 203 public DumpEvents() { 204 } 205 206 public void execute() { 207 Cursor cursor = mResolver.query(mEventsUri, null, null, null, null); 208 dumpCursor(cursor); 209 cursor.close(); 210 } 211 } 212 213 /** 214 * This command dumps the list of instances to the log for debugging. 215 */ 216 private class DumpInstances implements Command { 217 long begin; 218 long end; 219 220 public DumpInstances(String startDate, String endDate) { 221 Time time = new Time(DEFAULT_TIMEZONE); 222 time.parse3339(startDate); 223 begin = time.toMillis(false /* use isDst */); 224 time.parse3339(endDate); 225 end = time.toMillis(false /* use isDst */); 226 } 227 228 public void execute() { 229 Cursor cursor = queryInstances(begin, end); 230 dumpCursor(cursor); 231 cursor.close(); 232 } 233 } 234 235 /** 236 * This command queries the number of instances and compares it to the given 237 * expected value. 238 */ 239 private class QueryNumInstances implements Command { 240 int expected; 241 long begin; 242 long end; 243 244 public QueryNumInstances(String startDate, String endDate, int expected) { 245 Time time = new Time(DEFAULT_TIMEZONE); 246 time.parse3339(startDate); 247 begin = time.toMillis(false /* use isDst */); 248 time.parse3339(endDate); 249 end = time.toMillis(false /* use isDst */); 250 this.expected = expected; 251 } 252 253 public void execute() { 254 Cursor cursor = queryInstances(begin, end); 255 assertEquals(expected, cursor.getCount()); 256 cursor.close(); 257 } 258 } 259 260 /** 261 * When this command runs it verifies that all of the instances in the 262 * given range match the expected instances (each instance is specified by 263 * a start date). 264 * If you just want to verify that an instance exists in a given date 265 * range, use {@link VerifyInstance} instead. 266 */ 267 private class VerifyAllInstances implements Command { 268 long[] instances; 269 long begin; 270 long end; 271 272 public VerifyAllInstances(String startDate, String endDate, String[] dates) { 273 Time time = new Time(DEFAULT_TIMEZONE); 274 time.parse3339(startDate); 275 begin = time.toMillis(false /* use isDst */); 276 time.parse3339(endDate); 277 end = time.toMillis(false /* use isDst */); 278 279 if (dates == null) { 280 return; 281 } 282 283 // Convert all the instance date strings to UTC milliseconds 284 int len = dates.length; 285 this.instances = new long[len]; 286 int index = 0; 287 for (String instance : dates) { 288 time.parse3339(instance); 289 this.instances[index++] = time.toMillis(false /* use isDst */); 290 } 291 } 292 293 public void execute() { 294 Cursor cursor = queryInstances(begin, end); 295 int len = 0; 296 if (instances != null) { 297 len = instances.length; 298 } 299 if (len != cursor.getCount()) { 300 dumpCursor(cursor); 301 } 302 assertEquals("number of instances don't match", len, cursor.getCount()); 303 304 if (instances == null) { 305 return; 306 } 307 308 int beginColumn = cursor.getColumnIndex(Instances.BEGIN); 309 while (cursor.moveToNext()) { 310 long begin = cursor.getLong(beginColumn); 311 312 // Search the list of expected instances for a matching start 313 // time. 314 boolean found = false; 315 for (long instance : instances) { 316 if (instance == begin) { 317 found = true; 318 break; 319 } 320 } 321 if (!found) { 322 int titleColumn = cursor.getColumnIndex(Events.TITLE); 323 int allDayColumn = cursor.getColumnIndex(Events.ALL_DAY); 324 325 String title = cursor.getString(titleColumn); 326 boolean allDay = cursor.getInt(allDayColumn) != 0; 327 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | 328 DateUtils.FORMAT_24HOUR; 329 if (allDay) { 330 flags |= DateUtils.FORMAT_UTC; 331 } else { 332 flags |= DateUtils.FORMAT_SHOW_TIME; 333 } 334 String date = DateUtils.formatDateRange(mContext, begin, begin, flags); 335 String mesg = String.format("Test failed!" 336 + " unexpected instance (\"%s\") at %s", 337 title, date); 338 Log.e(TAG, mesg); 339 } 340 if (!found) { 341 dumpCursor(cursor); 342 } 343 assertTrue(found); 344 } 345 cursor.close(); 346 } 347 } 348 349 /** 350 * When this command runs it verifies that the given instance exists in 351 * the given date range. 352 */ 353 private class VerifyInstance implements Command { 354 long instance; 355 boolean allDay; 356 long begin; 357 long end; 358 359 /** 360 * Creates a command to check that the given range [startDate,endDate] 361 * contains a specific instance of an event (specified by "date"). 362 * 363 * @param startDate the beginning of the date range 364 * @param endDate the end of the date range 365 * @param date the date or date-time string of an event instance 366 */ 367 public VerifyInstance(String startDate, String endDate, String date) { 368 Time time = new Time(DEFAULT_TIMEZONE); 369 time.parse3339(startDate); 370 begin = time.toMillis(false /* use isDst */); 371 time.parse3339(endDate); 372 end = time.toMillis(false /* use isDst */); 373 374 // Convert the instance date string to UTC milliseconds 375 time.parse3339(date); 376 allDay = time.allDay; 377 instance = time.toMillis(false /* use isDst */); 378 } 379 380 public void execute() { 381 Cursor cursor = queryInstances(begin, end); 382 int beginColumn = cursor.getColumnIndex(Instances.BEGIN); 383 boolean found = false; 384 while (cursor.moveToNext()) { 385 long begin = cursor.getLong(beginColumn); 386 387 if (instance == begin) { 388 found = true; 389 break; 390 } 391 } 392 if (!found) { 393 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE; 394 if (allDay) { 395 flags |= DateUtils.FORMAT_UTC; 396 } else { 397 flags |= DateUtils.FORMAT_SHOW_TIME; 398 } 399 String date = DateUtils.formatDateRange(mContext, instance, instance, flags); 400 String mesg = String.format("Test failed!" 401 + " cannot find instance at %s", 402 date); 403 Log.e(TAG, mesg); 404 } 405 assertTrue(found); 406 cursor.close(); 407 } 408 } 409 410 /** 411 * This class stores all the useful information about an event. 412 */ 413 private class EventInfo { 414 String mTitle; 415 String mDescription; 416 String mTimezone; 417 boolean mAllDay; 418 long mDtstart; 419 long mDtend; 420 String mRrule; 421 String mDuration; 422 String mOriginalTitle; 423 long mOriginalInstance; 424 int mSyncId; 425 426 // Constructor for normal events, using the default timezone 427 public EventInfo(String title, String startDate, String endDate, 428 boolean allDay) { 429 init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE); 430 } 431 432 // Constructor for normal events, specifying the timezone 433 public EventInfo(String title, String startDate, String endDate, 434 boolean allDay, String timezone) { 435 init(title, startDate, endDate, allDay, timezone); 436 } 437 438 public void init(String title, String startDate, String endDate, 439 boolean allDay, String timezone) { 440 mTitle = title; 441 Time time = new Time(); 442 if (allDay) { 443 time.timezone = Time.TIMEZONE_UTC; 444 } else if (timezone != null) { 445 time.timezone = timezone; 446 } 447 mTimezone = time.timezone; 448 time.parse3339(startDate); 449 mDtstart = time.toMillis(false /* use isDst */); 450 time.parse3339(endDate); 451 mDtend = time.toMillis(false /* use isDst */); 452 mDuration = null; 453 mRrule = null; 454 mAllDay = allDay; 455 } 456 457 // Constructor for repeating events, using the default timezone 458 public EventInfo(String title, String description, String startDate, String endDate, 459 String rrule, boolean allDay) { 460 init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE); 461 } 462 463 // Constructor for repeating events, specifying the timezone 464 public EventInfo(String title, String description, String startDate, String endDate, 465 String rrule, boolean allDay, String timezone) { 466 init(title, description, startDate, endDate, rrule, allDay, timezone); 467 } 468 469 public void init(String title, String description, String startDate, String endDate, 470 String rrule, boolean allDay, String timezone) { 471 mTitle = title; 472 mDescription = description; 473 Time time = new Time(); 474 if (allDay) { 475 time.timezone = Time.TIMEZONE_UTC; 476 } else if (timezone != null) { 477 time.timezone = timezone; 478 } 479 mTimezone = time.timezone; 480 time.parse3339(startDate); 481 mDtstart = time.toMillis(false /* use isDst */); 482 if (endDate != null) { 483 time.parse3339(endDate); 484 mDtend = time.toMillis(false /* use isDst */); 485 } 486 if (allDay) { 487 long days = 1; 488 if (endDate != null) { 489 days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS; 490 } 491 mDuration = "P" + days + "D"; 492 } else { 493 long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS; 494 mDuration = "P" + seconds + "S"; 495 } 496 mRrule = rrule; 497 mAllDay = allDay; 498 } 499 500 // Constructor for recurrence exceptions, using the default timezone 501 public EventInfo(String originalTitle, String originalInstance, String title, 502 String description, String startDate, String endDate, boolean allDay) { 503 init(originalTitle, originalInstance, 504 title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE); 505 } 506 507 public void init(String originalTitle, String originalInstance, 508 String title, String description, String startDate, String endDate, 509 boolean allDay, String timezone) { 510 mOriginalTitle = originalTitle; 511 Time time = new Time(timezone); 512 time.parse3339(originalInstance); 513 mOriginalInstance = time.toMillis(false /* use isDst */); 514 init(title, description, startDate, endDate, null /* rrule */, allDay, timezone); 515 } 516 } 517 518 private class InstanceInfo { 519 EventInfo mEvent; 520 long mBegin; 521 long mEnd; 522 int mExpectedOccurrences; 523 524 public InstanceInfo(String eventName, String startDate, String endDate, int expected) { 525 // Find the test index that contains the given event name 526 mEvent = findEvent(eventName); 527 Time time = new Time(mEvent.mTimezone); 528 time.parse3339(startDate); 529 mBegin = time.toMillis(false /* use isDst */); 530 time.parse3339(endDate); 531 mEnd = time.toMillis(false /* use isDst */); 532 mExpectedOccurrences = expected; 533 } 534 } 535 536 /** 537 * This is the main table of events. The events in this table are 538 * referred to by name in other places. 539 */ 540 private EventInfo[] mEvents = { 541 new EventInfo("normal0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", false), 542 new EventInfo("normal1", "2008-05-26T08:30:00", "2008-05-26T09:30:00", false), 543 new EventInfo("normal2", "2008-05-26T14:30:00", "2008-05-26T15:30:00", false), 544 new EventInfo("allday0", "2008-05-02T00:00:00", "2008-05-03T00:00:00", true), 545 new EventInfo("allday1", "2008-05-02T00:00:00", "2008-05-31T00:00:00", true), 546 new EventInfo("daily0", "daily from 5/1/2008 12am to 1am", 547 "2008-05-01T00:00:00", "2008-05-01T01:00:00", 548 "FREQ=DAILY;WKST=SU", false), 549 new EventInfo("daily1", "daily from 5/1/2008 8:30am to 9:30am until 5/3/2008 8am", 550 "2008-05-01T08:30:00", "2008-05-01T09:30:00", 551 "FREQ=DAILY;UNTIL=20080503T150000Z;WKST=SU", false), 552 new EventInfo("daily2", "daily from 5/1/2008 8:45am to 9:15am until 5/3/2008 10am", 553 "2008-05-01T08:45:00", "2008-05-01T09:15:00", 554 "FREQ=DAILY;UNTIL=20080503T170000Z;WKST=SU", false), 555 new EventInfo("allday daily0", "all-day daily from 5/1/2008", 556 "2008-05-01", null, 557 "FREQ=DAILY;WKST=SU", true), 558 new EventInfo("allday daily1", "all-day daily from 5/1/2008 until 5/3/2008", 559 "2008-05-01", null, 560 "FREQ=DAILY;UNTIL=20080503T000000Z;WKST=SU", true), 561 new EventInfo("allday weekly0", "all-day weekly from 5/1/2008", 562 "2008-05-01", null, 563 "FREQ=WEEKLY;WKST=SU", true), 564 new EventInfo("allday weekly1", "all-day for 2 days weekly from 5/1/2008", 565 "2008-05-01", "2008-05-03", 566 "FREQ=WEEKLY;WKST=SU", true), 567 new EventInfo("allday yearly0", "all-day yearly on 5/1/2008", 568 "2008-05-01T", null, 569 "FREQ=YEARLY;WKST=SU", true), 570 new EventInfo("weekly0", "weekly from 5/6/2008 on Tue 1pm to 2pm", 571 "2008-05-06T13:00:00", "2008-05-06T14:00:00", 572 "FREQ=WEEKLY;BYDAY=TU;WKST=MO", false), 573 new EventInfo("weekly1", "every 2 weeks from 5/6/2008 on Tue from 2:30pm to 3:30pm", 574 "2008-05-06T14:30:00", "2008-05-06T15:30:00", 575 "FREQ=WEEKLY;INTERVAL=2;BYDAY=TU;WKST=MO", false), 576 new EventInfo("monthly0", "monthly from 5/20/2008 on the 3rd Tues from 3pm to 4pm", 577 "2008-05-20T15:00:00", "2008-05-20T16:00:00", 578 "FREQ=MONTHLY;BYDAY=3TU;WKST=SU", false), 579 new EventInfo("monthly1", "monthly from 5/1/2008 on the 1st from 12:00am to 12:10am", 580 "2008-05-01T00:00:00", "2008-05-01T00:10:00", 581 "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=1", false), 582 new EventInfo("monthly2", "monthly from 5/31/2008 on the 31st 11pm to midnight", 583 "2008-05-31T23:00:00", "2008-06-01T00:00:00", 584 "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=31", false), 585 new EventInfo("daily0", "2008-05-01T00:00:00", 586 "except0", "daily0 exception for 5/1/2008 12am, change to 5/1/2008 2am to 3am", 587 "2008-05-01T02:00:00", "2008-05-01T01:03:00", false), 588 new EventInfo("daily0", "2008-05-03T00:00:00", 589 "except1", "daily0 exception for 5/3/2008 12am, change to 5/3/2008 2am to 3am", 590 "2008-05-03T02:00:00", "2008-05-03T01:03:00", false), 591 new EventInfo("daily0", "2008-05-02T00:00:00", 592 "except2", "daily0 exception for 5/2/2008 12am, change to 1/2/2008", 593 "2008-01-02T00:00:00", "2008-01-02T01:00:00", false), 594 new EventInfo("weekly0", "2008-05-13T13:00:00", 595 "except3", "daily0 exception for 5/11/2008 1pm, change to 12/11/2008 1pm", 596 "2008-12-11T13:00:00", "2008-12-11T14:00:00", false), 597 new EventInfo("weekly0", "2008-05-13T13:00:00", 598 "cancel0", "weekly0 exception for 5/13/2008 1pm", 599 "2008-05-13T13:00:00", "2008-05-13T14:00:00", false), 600 new EventInfo("yearly0", "yearly on 5/1/2008 from 1pm to 2pm", 601 "2008-05-01T13:00:00", "2008-05-01T14:00:00", 602 "FREQ=YEARLY;WKST=SU", false), 603 }; 604 605 /** 606 * This table is used to create repeating events and then check that the 607 * number of instances within a given range matches the expected number 608 * of instances. 609 */ 610 private InstanceInfo[] mInstanceRanges = { 611 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-01T00:01:00", 1), 612 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-01T01:00:00", 1), 613 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 2), 614 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-02T23:59:00", 2), 615 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-02T00:01:00", 1), 616 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-02T01:00:00", 1), 617 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-03T00:00:00", 2), 618 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 31), 619 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-06-01T23:59:00", 32), 620 621 new InstanceInfo("daily1", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 1), 622 new InstanceInfo("daily1", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 2), 623 624 new InstanceInfo("daily2", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 1), 625 new InstanceInfo("daily2", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 3), 626 627 new InstanceInfo("allday daily0", "2008-05-01", "2008-05-07", 7), 628 new InstanceInfo("allday daily1", "2008-05-01", "2008-05-07", 3), 629 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-07", 1), 630 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-08", 2), 631 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-31", 5), 632 new InstanceInfo("allday weekly1", "2008-05-01", "2008-05-31", 5), 633 new InstanceInfo("allday yearly0", "2008-05-01", "2009-04-30", 1), 634 new InstanceInfo("allday yearly0", "2008-05-01", "2009-05-02", 2), 635 636 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 0), 637 new InstanceInfo("weekly0", "2008-05-06T00:00:00", "2008-05-07T00:00:00", 1), 638 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 4), 639 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 8), 640 641 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 0), 642 new InstanceInfo("weekly1", "2008-05-06T00:00:00", "2008-05-07T00:00:00", 1), 643 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 2), 644 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 4), 645 646 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-20T13:00:00", 0), 647 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-20T15:00:00", 1), 648 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-05-31T00:00:00", 0), 649 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-06-17T14:59:00", 0), 650 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-06-17T15:00:00", 1), 651 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 1), 652 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 2), 653 654 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-05-01T01:00:00", 1), 655 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 1), 656 new InstanceInfo("monthly1", "2008-05-01T00:10:00", "2008-05-31T23:59:00", 1), 657 new InstanceInfo("monthly1", "2008-05-01T00:11:00", "2008-05-31T23:59:00", 0), 658 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-06-01T00:00:00", 2), 659 660 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 0), 661 new InstanceInfo("monthly2", "2008-05-01T00:10:00", "2008-05-31T23:00:00", 1), 662 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-07-01T00:00:00", 1), 663 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-08-01T00:00:00", 2), 664 665 new InstanceInfo("yearly0", "2008-05-01", "2009-04-30", 1), 666 new InstanceInfo("yearly0", "2008-05-01", "2009-05-02", 2), 667 }; 668 669 /** 670 * This sequence of commands inserts and deletes some events. 671 */ 672 private Command[] mNormalInsertDelete = { 673 new Insert("normal0"), 674 new Insert("normal1"), 675 new Insert("normal2"), 676 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-31T00:01:00", 3), 677 new Delete("normal1", 1), 678 new QueryNumEvents(2), 679 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-31T00:01:00", 2), 680 new Delete("normal1", 0), 681 new Delete("normal2", 1), 682 new QueryNumEvents(1), 683 new Delete("normal0", 1), 684 new QueryNumEvents(0), 685 }; 686 687 /** 688 * This sequence of commands inserts and deletes some all-day events. 689 */ 690 private Command[] mAlldayInsertDelete = { 691 new Insert("allday0"), 692 new Insert("allday1"), 693 new QueryNumEvents(2), 694 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-01T00:01:00", 0), 695 new QueryNumInstances("2008-05-02T00:00:00", "2008-05-02T00:01:00", 2), 696 new QueryNumInstances("2008-05-03T00:00:00", "2008-05-03T00:01:00", 1), 697 new Delete("allday0", 1), 698 new QueryNumEvents(1), 699 new QueryNumInstances("2008-05-02T00:00:00", "2008-05-02T00:01:00", 1), 700 new QueryNumInstances("2008-05-03T00:00:00", "2008-05-03T00:01:00", 1), 701 new Delete("allday1", 1), 702 new QueryNumEvents(0), 703 }; 704 705 /** 706 * This sequence of commands inserts and deletes some repeating events. 707 */ 708 private Command[] mRecurringInsertDelete = { 709 new Insert("daily0"), 710 new Insert("daily1"), 711 new QueryNumEvents(2), 712 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-02T00:01:00", 3), 713 new QueryNumInstances("2008-05-01T01:01:00", "2008-05-02T00:01:00", 2), 714 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 6), 715 new Delete("daily1", 1), 716 new QueryNumEvents(1), 717 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-02T00:01:00", 2), 718 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 4), 719 new Delete("daily0", 1), 720 new QueryNumEvents(0), 721 }; 722 723 /** 724 * This sequence of commands creates a recurring event with a recurrence 725 * exception that moves an event outside the expansion window. It checks that the 726 * recurrence exception does not occur in the Instances database table. 727 * Bug 1642665 728 */ 729 private Command[] mExceptionWithMovedRecurrence = { 730 new Insert("daily0"), 731 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 732 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 733 "2008-05-03T00:00:00", }), 734 new Insert("except2"), 735 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 736 new String[] {"2008-05-01T00:00:00", "2008-05-03T00:00:00"}), 737 }; 738 739 /** 740 * This sequence of commands deletes (cancels) one instance of a recurrence. 741 */ 742 private Command[] mCancelInstance = { 743 new Insert("weekly0"), 744 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-22T00:01:00", 745 new String[] {"2008-05-06T13:00:00", "2008-05-13T13:00:00", 746 "2008-05-20T13:00:00", }), 747 new Insert("cancel0"), 748 new Update("cancel0", new KeyValue[] { 749 new KeyValue(Calendar.EventsColumns.STATUS, 750 "" + Calendar.EventsColumns.STATUS_CANCELED), 751 }), 752 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-22T00:01:00", 753 new String[] {"2008-05-06T13:00:00", 754 "2008-05-20T13:00:00", }), 755 }; 756 /** 757 * This sequence of commands creates a recurring event with a recurrence 758 * exception that moves an event from outside the expansion window into the 759 * expansion window. 760 */ 761 private Command[] mExceptionWithMovedRecurrence2 = { 762 new Insert("weekly0"), 763 new VerifyAllInstances("2008-12-01T00:00:00", "2008-12-22T00:01:00", 764 new String[] {"2008-12-02T13:00:00", "2008-12-09T13:00:00", 765 "2008-12-16T13:00:00", }), 766 new Insert("except3"), 767 new VerifyAllInstances("2008-12-01T00:00:00", "2008-12-22T00:01:00", 768 new String[] {"2008-12-02T13:00:00", "2008-12-09T13:00:00", 769 "2008-12-11T13:00:00", "2008-12-16T13:00:00", }), 770 }; 771 /** 772 * This sequence of commands creates a recurring event with a recurrence 773 * exception and then changes the end time of the recurring event. It then 774 * checks that the recurrence exception does not occur in the Instances 775 * database table. 776 */ 777 private Command[] 778 mExceptionWithTruncatedRecurrence = { 779 new Insert("daily0"), 780 // Verify 4 occurrences of the "daily0" repeating event 781 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 782 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 783 "2008-05-03T00:00:00", "2008-05-04T00:00:00"}), 784 new Insert("except1"), 785 new QueryNumEvents(2), 786 787 // Verify that one of the 4 occurrences has its start time changed 788 // so that it now matches the recurrence exception. 789 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 790 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 791 "2008-05-03T02:00:00", "2008-05-04T00:00:00"}), 792 793 // Change the end time of "daily0" but it still includes the 794 // recurrence exception. 795 new Update("daily0", new KeyValue[] { 796 new KeyValue(Events.RRULE, "FREQ=DAILY;UNTIL=20080505T150000Z;WKST=SU"), 797 }), 798 799 // Verify that the recurrence exception is still there 800 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 801 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 802 "2008-05-03T02:00:00", "2008-05-04T00:00:00"}), 803 // This time change the end time of "daily0" so that it excludes 804 // the recurrence exception. 805 new Update("daily0", new KeyValue[] { 806 new KeyValue(Events.RRULE, "FREQ=DAILY;UNTIL=20080502T150000Z;WKST=SU"), 807 }), 808 // The server will cancel the out-of-range exception. 809 // It would be nice for the provider to handle this automatically, 810 // but for now simulate the server-side cancel. 811 new Update("except1", new KeyValue[] { 812 new KeyValue(Calendar.EventsColumns.STATUS, "" + Calendar.EventsColumns.STATUS_CANCELED), 813 }), 814 // Verify that the recurrence exception does not appear. 815 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 816 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00"}), 817 }; 818 819 /** 820 * Bug 135848. Ensure that a recurrence exception is displayed even if the recurrence 821 * is not present. 822 */ 823 private Command[] mExceptionWithNoRecurrence = { 824 new Insert("except0"), 825 new QueryNumEvents(1), 826 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 827 new String[] {"2008-05-01T02:00:00"}), 828 }; 829 830 private EventInfo findEvent(String name) { 831 int len = mEvents.length; 832 for (int ii = 0; ii < len; ii++) { 833 EventInfo event = mEvents[ii]; 834 if (name.equals(event.mTitle)) { 835 return event; 836 } 837 } 838 return null; 839 } 840 841 public CalendarProvider2Test() { 842 super(CalendarProvider2ForTesting.class, Calendar.AUTHORITY); 843 } 844 845 @Override 846 protected void setUp() throws Exception { 847 super.setUp(); 848 849 mContext = getMockContext(); 850 mResolver = getMockContentResolver(); 851 mResolver.addProvider("subscribedfeeds", new MockProvider("subscribedfeeds")); 852 mResolver.addProvider("sync", new MockProvider("sync")); 853 854 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 855 mDb = helper.getWritableDatabase(); 856 wipeData(mDb); 857 mMetaData = getProvider().mMetaData; 858 mForceDtend = false; 859 initCalendarCache(); 860 } 861 862 863 public void wipeData(SQLiteDatabase db) { 864 db.execSQL("DELETE FROM Calendars;"); 865 db.execSQL("DELETE FROM Events;"); 866 db.execSQL("DELETE FROM EventsRawTimes;"); 867 db.execSQL("DELETE FROM Instances;"); 868 db.execSQL("DELETE FROM CalendarMetaData;"); 869 db.execSQL("DELETE FROM CalendarCache;"); 870 db.execSQL("DELETE FROM Attendees;"); 871 db.execSQL("DELETE FROM Reminders;"); 872 db.execSQL("DELETE FROM CalendarAlerts;"); 873 db.execSQL("DELETE FROM ExtendedProperties;"); 874 } 875 876 @Override 877 protected void tearDown() throws Exception { 878 mDb.close(); 879 mDb = null; 880 getProvider().getDatabaseHelper().close(); 881 super.tearDown(); 882 } 883 884 /** 885 * Dumps the contents of the given cursor to the log. For debugging. 886 * @param cursor the database cursor 887 */ 888 private void dumpCursor(Cursor cursor) { 889 cursor.moveToPosition(-1); 890 String[] cols = cursor.getColumnNames(); 891 892 Log.i(TAG, "dumpCursor() count: " + cursor.getCount()); 893 int index = 0; 894 while (cursor.moveToNext()) { 895 Log.i(TAG, index + " {"); 896 for (int i = 0; i < cols.length; i++) { 897 Log.i(TAG, " " + cols[i] + '=' + cursor.getString(i)); 898 } 899 Log.i(TAG, "}"); 900 index += 1; 901 } 902 cursor.moveToPosition(-1); 903 } 904 905 private int insertCal(String name, String timezone) { 906 return insertCal(name, timezone, "joe@joe.com"); 907 } 908 909 private int insertCal(String name, String timezone, String account) { 910 ContentValues m = new ContentValues(); 911 m.put(Calendars.NAME, name); 912 m.put(Calendars.DISPLAY_NAME, name); 913 m.put(Calendars.COLOR, "0xff123456"); 914 m.put(Calendars.TIMEZONE, timezone); 915 m.put(Calendars.SELECTED, 1); 916 m.put(Calendars.URL, CALENDAR_URL); 917 m.put(Calendars.OWNER_ACCOUNT, account); 918 m.put(Calendars._SYNC_ACCOUNT, account); 919 m.put(Calendars._SYNC_ACCOUNT_TYPE, "com.google"); 920 m.put(Calendars.SYNC_EVENTS, 1); 921 922 Uri url = mResolver.insert(Calendar.Calendars.CONTENT_URI, m); 923 String id = url.getLastPathSegment(); 924 return Integer.parseInt(id); 925 } 926 927 private Uri insertEvent(int calId, EventInfo event) { 928 if (mWipe) { 929 // Wipe instance table so it will be regenerated 930 mMetaData.clearInstanceRange(); 931 } 932 ContentValues m = new ContentValues(); 933 m.put(Events.CALENDAR_ID, calId); 934 m.put(Events.TITLE, event.mTitle); 935 m.put(Events.DTSTART, event.mDtstart); 936 m.put(Events.ALL_DAY, event.mAllDay ? 1 : 0); 937 938 if (event.mRrule == null || mForceDtend) { 939 // This is a normal event 940 m.put(Events.DTEND, event.mDtend); 941 } 942 if (event.mRrule != null) { 943 // This is a repeating event 944 m.put(Events.RRULE, event.mRrule); 945 m.put(Events.DURATION, event.mDuration); 946 } 947 948 if (event.mDescription != null) { 949 m.put(Events.DESCRIPTION, event.mDescription); 950 } 951 if (event.mTimezone != null) { 952 m.put(Events.EVENT_TIMEZONE, event.mTimezone); 953 } 954 955 if (event.mOriginalTitle != null) { 956 // This is a recurrence exception. 957 EventInfo recur = findEvent(event.mOriginalTitle); 958 assertNotNull(recur); 959 String syncId = String.format("%d", recur.mSyncId); 960 m.put(Events.ORIGINAL_EVENT, syncId); 961 m.put(Events.ORIGINAL_ALL_DAY, recur.mAllDay ? 1 : 0); 962 m.put(Events.ORIGINAL_INSTANCE_TIME, event.mOriginalInstance); 963 } 964 Uri url = mResolver.insert(mEventsUri, m); 965 966 // Create a fake _sync_id and add it to the event. Update the database 967 // directly so that we don't trigger any validation checks in the 968 // CalendarProvider. 969 long id = ContentUris.parseId(url); 970 mDb.execSQL("UPDATE Events SET _sync_id=" + mGlobalSyncId + " WHERE _id=" + id); 971 event.mSyncId = mGlobalSyncId; 972 mGlobalSyncId += 1; 973 974 return url; 975 } 976 977 /** 978 * Deletes all the events that match the given title. 979 * @param title the given title to match events on 980 * @return the number of rows deleted 981 */ 982 private int deleteMatchingEvents(String title) { 983 Cursor cursor = mResolver.query(mEventsUri, new String[] { Events._ID }, 984 "title=?", new String[] { title }, null); 985 int numRows = 0; 986 while (cursor.moveToNext()) { 987 long id = cursor.getLong(0); 988 // Do delete as a sync adapter so event is really deleted, not just marked 989 // as deleted. 990 Uri uri = updatedUri(ContentUris.withAppendedId(Events.CONTENT_URI, id), true); 991 numRows += mResolver.delete(uri, null, null); 992 } 993 cursor.close(); 994 return numRows; 995 } 996 997 /** 998 * Updates all the events that match the given title. 999 * @param title the given title to match events on 1000 * @return the number of rows updated 1001 */ 1002 private int updateMatchingEvents(String title, ContentValues values) { 1003 String[] projection = new String[] { 1004 Events._ID, 1005 Events.DTSTART, 1006 Events.DTEND, 1007 Events.DURATION, 1008 Events.ALL_DAY, 1009 Events.RRULE, 1010 Events.EVENT_TIMEZONE, 1011 Events.ORIGINAL_EVENT, 1012 }; 1013 Cursor cursor = mResolver.query(mEventsUri, projection, 1014 "title=?", new String[] { title }, null); 1015 int numRows = 0; 1016 while (cursor.moveToNext()) { 1017 long id = cursor.getLong(0); 1018 1019 // If any of the following fields are being changed, then we need 1020 // to include all of them. 1021 if (values.containsKey(Events.DTSTART) || values.containsKey(Events.DTEND) 1022 || values.containsKey(Events.DURATION) || values.containsKey(Events.ALL_DAY) 1023 || values.containsKey(Events.RRULE) 1024 || values.containsKey(Events.EVENT_TIMEZONE) 1025 || values.containsKey(Calendar.EventsColumns.STATUS)) { 1026 long dtstart = cursor.getLong(1); 1027 long dtend = cursor.getLong(2); 1028 String duration = cursor.getString(3); 1029 boolean allDay = cursor.getInt(4) != 0; 1030 String rrule = cursor.getString(5); 1031 String timezone = cursor.getString(6); 1032 String originalEvent = cursor.getString(7); 1033 1034 if (!values.containsKey(Events.DTSTART)) { 1035 values.put(Events.DTSTART, dtstart); 1036 } 1037 // Don't add DTEND for repeating events 1038 if (!values.containsKey(Events.DTEND) && rrule == null) { 1039 values.put(Events.DTEND, dtend); 1040 } 1041 if (!values.containsKey(Events.DURATION) && duration != null) { 1042 values.put(Events.DURATION, duration); 1043 } 1044 if (!values.containsKey(Events.ALL_DAY)) { 1045 values.put(Events.ALL_DAY, allDay ? 1 : 0); 1046 } 1047 if (!values.containsKey(Events.RRULE) && rrule != null) { 1048 values.put(Events.RRULE, rrule); 1049 } 1050 if (!values.containsKey(Events.EVENT_TIMEZONE) && timezone != null) { 1051 values.put(Events.EVENT_TIMEZONE, timezone); 1052 } 1053 if (!values.containsKey(Events.ORIGINAL_EVENT) && originalEvent != null) { 1054 values.put(Events.ORIGINAL_EVENT, originalEvent); 1055 } 1056 } 1057 1058 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, id); 1059 numRows += mResolver.update(uri, values, null, null); 1060 } 1061 cursor.close(); 1062 return numRows; 1063 } 1064 1065 private void deleteAllEvents() { 1066 mDb.execSQL("DELETE FROM Events;"); 1067 mMetaData.clearInstanceRange(); 1068 } 1069 1070 private void initCalendarCache() throws CalendarCache.CacheException { 1071 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 1072 cleanCalendarDataTable(helper); 1073 CalendarCache cache = new CalendarCache(helper); 1074 1075 String localTimezone = TimeZone.getDefault().getID(); 1076 1077 // Set initial values 1078 cache.writeTimezoneDatabaseVersion("2010k"); 1079 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 1080 cache.writeTimezoneInstances(localTimezone); 1081 cache.writeTimezoneInstancesPrevious(localTimezone); 1082 } 1083 1084 public void testInsertNormalEvents() throws Exception { 1085 Cursor cursor; 1086 Uri url = null; 1087 1088 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1089 1090 cursor = mResolver.query(mEventsUri, null, null, null, null); 1091 assertEquals(0, cursor.getCount()); 1092 cursor.close(); 1093 1094 // Keep track of the number of normal events 1095 int numEvents = 0; 1096 1097 // "begin" is the earliest start time of all the normal events, 1098 // and "end" is the latest end time of all the normal events. 1099 long begin = 0, end = 0; 1100 1101 int len = mEvents.length; 1102 for (int ii = 0; ii < len; ii++) { 1103 EventInfo event = mEvents[ii]; 1104 // Skip repeating events and recurrence exceptions 1105 if (event.mRrule != null || event.mOriginalTitle != null) { 1106 continue; 1107 } 1108 if (numEvents == 0) { 1109 begin = event.mDtstart; 1110 end = event.mDtend; 1111 } else { 1112 if (begin > event.mDtstart) { 1113 begin = event.mDtstart; 1114 } 1115 if (end < event.mDtend) { 1116 end = event.mDtend; 1117 } 1118 } 1119 url = insertEvent(calId, event); 1120 numEvents += 1; 1121 } 1122 1123 // query one 1124 cursor = mResolver.query(url, null, null, null, null); 1125 assertEquals(1, cursor.getCount()); 1126 cursor.close(); 1127 1128 // query all 1129 cursor = mResolver.query(mEventsUri, null, null, null, null); 1130 assertEquals(numEvents, cursor.getCount()); 1131 cursor.close(); 1132 1133 // Check that the Instances table has one instance of each of the 1134 // normal events. 1135 cursor = queryInstances(begin, end); 1136 assertEquals(numEvents, cursor.getCount()); 1137 cursor.close(); 1138 } 1139 1140 public void testInsertRepeatingEvents() throws Exception { 1141 Cursor cursor; 1142 Uri url = null; 1143 1144 int calId = insertCal("Calendar0", "America/Los_Angeles"); 1145 1146 cursor = mResolver.query(mEventsUri, null, null, null, null); 1147 assertEquals(0, cursor.getCount()); 1148 cursor.close(); 1149 1150 // Keep track of the number of repeating events 1151 int numEvents = 0; 1152 1153 int len = mEvents.length; 1154 for (int ii = 0; ii < len; ii++) { 1155 EventInfo event = mEvents[ii]; 1156 // Skip normal events 1157 if (event.mRrule == null) { 1158 continue; 1159 } 1160 url = insertEvent(calId, event); 1161 numEvents += 1; 1162 } 1163 1164 // query one 1165 cursor = mResolver.query(url, null, null, null, null); 1166 assertEquals(1, cursor.getCount()); 1167 cursor.close(); 1168 1169 // query all 1170 cursor = mResolver.query(mEventsUri, null, null, null, null); 1171 assertEquals(numEvents, cursor.getCount()); 1172 cursor.close(); 1173 } 1174 1175 // Force a dtend value to be set and make sure instance expansion still works 1176 public void testInstanceRangeDtend() throws Exception { 1177 mForceDtend = true; 1178 testInstanceRange(); 1179 } 1180 1181 public void testInstanceRange() throws Exception { 1182 Cursor cursor; 1183 Uri url = null; 1184 1185 int calId = insertCal("Calendar0", "America/Los_Angeles"); 1186 1187 cursor = mResolver.query(mEventsUri, null, null, null, null); 1188 assertEquals(0, cursor.getCount()); 1189 cursor.close(); 1190 1191 int len = mInstanceRanges.length; 1192 for (int ii = 0; ii < len; ii++) { 1193 InstanceInfo instance = mInstanceRanges[ii]; 1194 EventInfo event = instance.mEvent; 1195 url = insertEvent(calId, event); 1196 cursor = queryInstances(instance.mBegin, instance.mEnd); 1197 if (instance.mExpectedOccurrences != cursor.getCount()) { 1198 Log.e(TAG, "Test failed! Instance index: " + ii); 1199 Log.e(TAG, "title: " + event.mTitle + " desc: " + event.mDescription 1200 + " [begin,end]: [" + instance.mBegin + " " + instance.mEnd + "]" 1201 + " expected: " + instance.mExpectedOccurrences); 1202 dumpCursor(cursor); 1203 } 1204 assertEquals(instance.mExpectedOccurrences, cursor.getCount()); 1205 cursor.close(); 1206 // Delete as sync_adapter so event is really deleted. 1207 int rows = mResolver.delete(updatedUri(url, true), 1208 null /* selection */, null /* selection args */); 1209 assertEquals(1, rows); 1210 } 1211 } 1212 1213 public void testEntityQuery() throws Exception { 1214 testInsertNormalEvents(); // To initialize 1215 1216 ContentValues reminder = new ContentValues(); 1217 reminder.put(Calendar.Reminders.EVENT_ID, 1); 1218 reminder.put(Calendar.Reminders.MINUTES, 10); 1219 reminder.put(Calendar.Reminders.METHOD, Calendar.Reminders.METHOD_SMS); 1220 mResolver.insert(Calendar.Reminders.CONTENT_URI, reminder); 1221 reminder.put(Calendar.Reminders.MINUTES, 20); 1222 mResolver.insert(Calendar.Reminders.CONTENT_URI, reminder); 1223 1224 ContentValues extended = new ContentValues(); 1225 extended.put(Calendar.ExtendedProperties.NAME, "foo"); 1226 extended.put(Calendar.ExtendedProperties.VALUE, "bar"); 1227 extended.put(Calendar.ExtendedProperties.EVENT_ID, 2); 1228 mResolver.insert(Calendar.ExtendedProperties.CONTENT_URI, extended); 1229 extended.put(Calendar.ExtendedProperties.EVENT_ID, 1); 1230 mResolver.insert(Calendar.ExtendedProperties.CONTENT_URI, extended); 1231 extended.put(Calendar.ExtendedProperties.NAME, "foo2"); 1232 extended.put(Calendar.ExtendedProperties.VALUE, "bar2"); 1233 mResolver.insert(Calendar.ExtendedProperties.CONTENT_URI, extended); 1234 1235 ContentValues attendee = new ContentValues(); 1236 attendee.put(Calendar.Attendees.ATTENDEE_NAME, "Joe"); 1237 attendee.put(Calendar.Attendees.ATTENDEE_EMAIL, "joe@joe.com"); 1238 attendee.put(Calendar.Attendees.ATTENDEE_STATUS, 1239 Calendar.Attendees.ATTENDEE_STATUS_DECLINED); 1240 attendee.put(Calendar.Attendees.ATTENDEE_TYPE, Calendar.Attendees.TYPE_REQUIRED); 1241 attendee.put(Calendar.Attendees.ATTENDEE_RELATIONSHIP, 1242 Calendar.Attendees.RELATIONSHIP_PERFORMER); 1243 attendee.put(Calendar.Attendees.EVENT_ID, 3); 1244 mResolver.insert(Calendar.Attendees.CONTENT_URI, attendee); 1245 1246 EntityIterator ei = EventsEntity.newEntityIterator( 1247 mResolver.query(EventsEntity.CONTENT_URI, null, null, null, null), mResolver); 1248 int count = 0; 1249 try { 1250 while (ei.hasNext()) { 1251 Entity entity = ei.next(); 1252 ContentValues values = entity.getEntityValues(); 1253 assertEquals(CALENDAR_URL, values.getAsString(Calendars.URL)); 1254 ArrayList<Entity.NamedContentValues> subvalues = entity.getSubValues(); 1255 switch (values.getAsInteger("_id")) { 1256 case 1: 1257 assertEquals(5, subvalues.size()); // 2 x reminder, 3 x extended properties 1258 break; 1259 case 2: 1260 // Extended properties (contains originalTimezone) 1261 assertEquals(2, subvalues.size()); 1262 ContentValues subContentValues = subvalues.get(1).values; 1263 String name = subContentValues.getAsString( 1264 Calendar.ExtendedProperties.NAME); 1265 String value = subContentValues.getAsString( 1266 Calendar.ExtendedProperties.VALUE); 1267 assertEquals("foo", name); 1268 assertEquals("bar", value); 1269 break; 1270 case 3: 1271 assertEquals(2, subvalues.size()); // Attendees 1272 break; 1273 default: 1274 assertEquals(1, subvalues.size()); 1275 break; 1276 } 1277 count += 1; 1278 } 1279 assertEquals(5, count); 1280 } finally { 1281 ei.close(); 1282 } 1283 1284 ei = EventsEntity.newEntityIterator( 1285 mResolver.query(EventsEntity.CONTENT_URI, null, "_id = 3", null, null), 1286 mResolver); 1287 try { 1288 count = 0; 1289 while (ei.hasNext()) { 1290 Entity entity = ei.next(); 1291 count += 1; 1292 } 1293 assertEquals(1, count); 1294 } finally { 1295 ei.close(); 1296 } 1297 } 1298 1299 public void testDeleteCalendar() throws Exception { 1300 int calendarId0 = insertCal("Calendar0", DEFAULT_TIMEZONE); 1301 int calendarId1 = insertCal("Calendar1", DEFAULT_TIMEZONE, "user2@google.com"); 1302 insertEvent(calendarId0, mEvents[0]); 1303 insertEvent(calendarId1, mEvents[1]); 1304 // Should have 2 calendars and 2 events 1305 testQueryCount(Calendar.Calendars.CONTENT_URI, null /* where */, 2); 1306 testQueryCount(Calendar.Events.CONTENT_URI, null /* where */, 2); 1307 1308 int deletes = mResolver.delete(Calendar.Calendars.CONTENT_URI, 1309 "ownerAccount='user2@google.com'", null /* selectionArgs */); 1310 1311 assertEquals(1, deletes); 1312 // Should have 1 calendar and 1 event 1313 testQueryCount(Calendar.Calendars.CONTENT_URI, null /* where */, 1); 1314 testQueryCount(Calendar.Events.CONTENT_URI, null /* where */, 1); 1315 1316 deletes = mResolver.delete(Uri.withAppendedPath(Calendar.Calendars.CONTENT_URI, 1317 String.valueOf(calendarId0)), 1318 null /* selection*/ , null /* selectionArgs */); 1319 1320 assertEquals(1, deletes); 1321 // Should have 0 calendars and 0 events 1322 testQueryCount(Calendar.Calendars.CONTENT_URI, null /* where */, 0); 1323 testQueryCount(Calendar.Events.CONTENT_URI, null /* where */, 0); 1324 1325 deletes = mResolver.delete(Calendar.Calendars.CONTENT_URI, 1326 "ownerAccount=?", new String[] {"user2@google.com"} /* selectionArgs */); 1327 1328 assertEquals(0, deletes); 1329 } 1330 1331 public void testCalendarAlerts() throws Exception { 1332 // This projection is from AlertActivity; want to make sure it works. 1333 String[] projection = new String[] { 1334 Calendar.CalendarAlerts._ID, // 0 1335 Calendar.CalendarAlerts.TITLE, // 1 1336 Calendar.CalendarAlerts.EVENT_LOCATION, // 2 1337 Calendar.CalendarAlerts.ALL_DAY, // 3 1338 Calendar.CalendarAlerts.BEGIN, // 4 1339 Calendar.CalendarAlerts.END, // 5 1340 Calendar.CalendarAlerts.EVENT_ID, // 6 1341 Calendar.CalendarAlerts.COLOR, // 7 1342 Calendar.CalendarAlerts.RRULE, // 8 1343 Calendar.CalendarAlerts.HAS_ALARM, // 9 1344 Calendar.CalendarAlerts.STATE, // 10 1345 Calendar.CalendarAlerts.ALARM_TIME, // 11 1346 }; 1347 testInsertNormalEvents(); // To initialize 1348 1349 Uri alertUri = Calendar.CalendarAlerts.insert(mResolver, 1 /* eventId */, 1350 2 /* begin */, 3 /* end */, 4 /* alarmTime */, 5 /* minutes */); 1351 Calendar.CalendarAlerts.insert(mResolver, 1 /* eventId */, 1352 2 /* begin */, 7 /* end */, 8 /* alarmTime */, 9 /* minutes */); 1353 1354 // Regular query 1355 Cursor cursor = mResolver.query(Calendar.CalendarAlerts.CONTENT_URI, projection, 1356 null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1357 1358 assertEquals(2, cursor.getCount()); 1359 cursor.close(); 1360 1361 // Instance query 1362 cursor = mResolver.query(alertUri, projection, 1363 null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1364 1365 assertEquals(1, cursor.getCount()); 1366 cursor.close(); 1367 1368 // Grouped by event query 1369 cursor = mResolver.query(Calendar.CalendarAlerts.CONTENT_URI_BY_INSTANCE, projection, 1370 null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1371 1372 assertEquals(1, cursor.getCount()); 1373 cursor.close(); 1374 } 1375 1376 /** 1377 * Test attendee processing 1378 * @throws Exception 1379 */ 1380 public void testAttendees() throws Exception { 1381 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1382 1383 Uri eventUri = insertEvent(mCalendarId, findEvent("daily0")); 1384 long eventId = ContentUris.parseId(eventUri); 1385 1386 ContentValues attendee = new ContentValues(); 1387 attendee.put(Calendar.Attendees.ATTENDEE_NAME, "Joe"); 1388 attendee.put(Calendar.Attendees.ATTENDEE_EMAIL, "joe@joe.com"); 1389 attendee.put(Calendar.Attendees.ATTENDEE_TYPE, Calendar.Attendees.TYPE_REQUIRED); 1390 attendee.put(Calendar.Attendees.ATTENDEE_RELATIONSHIP, 1391 Calendar.Attendees.RELATIONSHIP_ORGANIZER); 1392 attendee.put(Calendar.Attendees.EVENT_ID, eventId); 1393 Uri attendeesUri = mResolver.insert(Calendar.Attendees.CONTENT_URI, attendee); 1394 1395 Cursor cursor = mResolver.query(Calendar.Attendees.CONTENT_URI, null, 1396 "event_id=" + eventId, null, null); 1397 assertEquals("Created event is missing", 1, cursor.getCount()); 1398 cursor.close(); 1399 1400 cursor = mResolver.query(eventUri, null, null, null, null); 1401 assertEquals("Created event is missing", 1, cursor.getCount()); 1402 int selfColumn = cursor.getColumnIndex(Calendar.Events.SELF_ATTENDEE_STATUS); 1403 cursor.moveToNext(); 1404 long selfAttendeeStatus = cursor.getInt(selfColumn); 1405 assertEquals(Calendar.Attendees.ATTENDEE_STATUS_ACCEPTED, selfAttendeeStatus); 1406 cursor.close(); 1407 1408 // Change status to declined 1409 attendee.put(Calendar.Attendees.ATTENDEE_STATUS, 1410 Calendar.Attendees.ATTENDEE_STATUS_DECLINED); 1411 mResolver.update(attendeesUri, attendee, null, null); 1412 1413 cursor = mResolver.query(eventUri, null, null, null, null); 1414 cursor.moveToNext(); 1415 selfAttendeeStatus = cursor.getInt(selfColumn); 1416 assertEquals(Calendar.Attendees.ATTENDEE_STATUS_DECLINED, selfAttendeeStatus); 1417 cursor.close(); 1418 1419 // Add another attendee 1420 attendee.put(Calendar.Attendees.ATTENDEE_NAME, "Dude"); 1421 attendee.put(Calendar.Attendees.ATTENDEE_EMAIL, "dude@dude.com"); 1422 attendee.put(Calendar.Attendees.ATTENDEE_STATUS, 1423 Calendar.Attendees.ATTENDEE_STATUS_ACCEPTED); 1424 mResolver.insert(Calendar.Attendees.CONTENT_URI, attendee); 1425 1426 cursor = mResolver.query(Calendar.Attendees.CONTENT_URI, null, 1427 "event_id=" + mCalendarId, null, null); 1428 assertEquals(2, cursor.getCount()); 1429 cursor.close(); 1430 1431 cursor = mResolver.query(eventUri, null, null, null, null); 1432 cursor.moveToNext(); 1433 selfAttendeeStatus = cursor.getInt(selfColumn); 1434 assertEquals(Calendar.Attendees.ATTENDEE_STATUS_DECLINED, selfAttendeeStatus); 1435 cursor.close(); 1436 } 1437 1438 1439 /** 1440 * Test the event's _sync_dirty status and clear it. 1441 * @param eventId event to fetch. 1442 * @param wanted the wanted _sync_dirty status 1443 */ 1444 private void testAndClearDirty(long eventId, int wanted) { 1445 Cursor cursor = mResolver.query( 1446 ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId), 1447 null, null, null, null); 1448 try { 1449 assertEquals("Event count", 1, cursor.getCount()); 1450 cursor.moveToNext(); 1451 int dirty = cursor.getInt(cursor.getColumnIndex(Calendar.Events._SYNC_DIRTY)); 1452 assertEquals("dirty flag", wanted, dirty); 1453 if (dirty == 1) { 1454 // Have to access database directly since provider will set dirty again. 1455 mDb.execSQL("UPDATE Events SET _sync_dirty=0 WHERE _id=" + eventId); 1456 } 1457 } finally { 1458 cursor.close(); 1459 } 1460 } 1461 1462 /** 1463 * Test the count of results from a query. 1464 * @param uri The URI to query 1465 * @param where The where string or null. 1466 * @param wanted The number of results wanted. An assertion is thrown if it doesn't match. 1467 */ 1468 private void testQueryCount(Uri uri, String where, int wanted) { 1469 Cursor cursor = mResolver.query(uri, null/* projection */, where, null /* selectionArgs */, 1470 null /* sortOrder */); 1471 try { 1472 assertEquals("query results", wanted, cursor.getCount()); 1473 } finally { 1474 cursor.close(); 1475 } 1476 } 1477 1478 /** 1479 * Test dirty flag processing. 1480 * @throws Exception 1481 */ 1482 public void testDirty() throws Exception { 1483 internalTestDirty(false); 1484 } 1485 1486 /** 1487 * Test dirty flag processing for updates from a sync adapter. 1488 * @throws Exception 1489 */ 1490 public void testDirtyWithSyncAdapter() throws Exception { 1491 internalTestDirty(true); 1492 } 1493 1494 /** 1495 * Add CALLER_IS_SYNCADAPTER to URI if this is a sync adapter operation. 1496 */ 1497 private Uri updatedUri(Uri uri, boolean syncAdapter) { 1498 if (syncAdapter) { 1499 return uri.buildUpon().appendQueryParameter(Calendar.CALLER_IS_SYNCADAPTER, "true") 1500 .build(); 1501 } else { 1502 return uri; 1503 } 1504 } 1505 1506 /** 1507 * Test dirty flag processing either for syncAdapter operations or client operations. 1508 * The main difference is syncAdapter operations don't set the dirty bit. 1509 */ 1510 private void internalTestDirty(boolean syncAdapter) throws Exception { 1511 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1512 1513 Uri eventUri = insertEvent(mCalendarId, findEvent("daily0")); 1514 1515 long eventId = ContentUris.parseId(eventUri); 1516 testAndClearDirty(eventId, 1); 1517 1518 ContentValues attendee = new ContentValues(); 1519 attendee.put(Calendar.Attendees.ATTENDEE_NAME, "Joe"); 1520 attendee.put(Calendar.Attendees.ATTENDEE_EMAIL, "joe@joe.com"); 1521 attendee.put(Calendar.Attendees.ATTENDEE_TYPE, Calendar.Attendees.TYPE_REQUIRED); 1522 attendee.put(Calendar.Attendees.ATTENDEE_RELATIONSHIP, 1523 Calendar.Attendees.RELATIONSHIP_ORGANIZER); 1524 attendee.put(Calendar.Attendees.EVENT_ID, eventId); 1525 1526 Uri attendeeUri = mResolver.insert( 1527 updatedUri(Calendar.Attendees.CONTENT_URI, syncAdapter), 1528 attendee); 1529 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1530 testQueryCount(Calendar.Attendees.CONTENT_URI, "event_id=" + eventId, 1); 1531 1532 ContentValues reminder = new ContentValues(); 1533 reminder.put(Calendar.Reminders.MINUTES, 10); 1534 reminder.put(Calendar.Reminders.METHOD, Calendar.Reminders.METHOD_EMAIL); 1535 reminder.put(Calendar.Attendees.EVENT_ID, eventId); 1536 1537 Uri reminderUri = mResolver.insert( 1538 updatedUri(Calendar.Reminders.CONTENT_URI, syncAdapter), reminder); 1539 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1540 testQueryCount(Calendar.Reminders.CONTENT_URI, "event_id=" + eventId, 1); 1541 1542 ContentValues alert = new ContentValues(); 1543 alert.put(Calendar.CalendarAlerts.BEGIN, 10); 1544 alert.put(Calendar.CalendarAlerts.END, 20); 1545 alert.put(Calendar.CalendarAlerts.ALARM_TIME, 30); 1546 alert.put(Calendar.CalendarAlerts.CREATION_TIME, 40); 1547 alert.put(Calendar.CalendarAlerts.RECEIVED_TIME, 50); 1548 alert.put(Calendar.CalendarAlerts.NOTIFY_TIME, 60); 1549 alert.put(Calendar.CalendarAlerts.STATE, Calendar.CalendarAlerts.SCHEDULED); 1550 alert.put(Calendar.CalendarAlerts.MINUTES, 30); 1551 alert.put(Calendar.CalendarAlerts.EVENT_ID, eventId); 1552 1553 Uri alertUri = mResolver.insert( 1554 updatedUri(Calendar.CalendarAlerts.CONTENT_URI, syncAdapter), alert); 1555 // Alerts don't dirty the event 1556 testAndClearDirty(eventId, 0); 1557 testQueryCount(Calendar.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 1); 1558 1559 ContentValues extended = new ContentValues(); 1560 extended.put(Calendar.ExtendedProperties.NAME, "foo"); 1561 extended.put(Calendar.ExtendedProperties.VALUE, "bar"); 1562 extended.put(Calendar.ExtendedProperties.EVENT_ID, eventId); 1563 1564 Uri extendedUri = mResolver.insert( 1565 updatedUri(Calendar.ExtendedProperties.CONTENT_URI, syncAdapter), extended); 1566 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1567 testQueryCount(Calendar.ExtendedProperties.CONTENT_URI, "event_id=" + eventId, 2); 1568 1569 // Now test updates 1570 1571 attendee = new ContentValues(); 1572 attendee.put(Calendar.Attendees.ATTENDEE_NAME, "Sam"); 1573 // Need to include EVENT_ID with attendee update. Is that desired? 1574 attendee.put(Calendar.Attendees.EVENT_ID, eventId); 1575 1576 assertEquals("update", 1, mResolver.update(updatedUri(attendeeUri, syncAdapter), attendee, 1577 null /* where */, null /* selectionArgs */)); 1578 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1579 1580 testQueryCount(Calendar.Attendees.CONTENT_URI, "event_id=" + eventId, 1); 1581 1582 reminder = new ContentValues(); 1583 reminder.put(Calendar.Reminders.MINUTES, 20); 1584 1585 assertEquals("update", 1, mResolver.update(updatedUri(reminderUri, syncAdapter), reminder, 1586 null /* where */, null /* selectionArgs */)); 1587 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1588 testQueryCount(Calendar.Reminders.CONTENT_URI, "event_id=" + eventId, 1); 1589 1590 alert = new ContentValues(); 1591 alert.put(Calendar.CalendarAlerts.STATE, Calendar.CalendarAlerts.DISMISSED); 1592 1593 assertEquals("update", 1, mResolver.update(updatedUri(alertUri, syncAdapter), alert, 1594 null /* where */, null /* selectionArgs */)); 1595 // Alerts don't dirty the event 1596 testAndClearDirty(eventId, 0); 1597 testQueryCount(Calendar.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 1); 1598 1599 extended = new ContentValues(); 1600 extended.put(Calendar.ExtendedProperties.VALUE, "baz"); 1601 1602 assertEquals("update", 1, mResolver.update(updatedUri(extendedUri, syncAdapter), extended, 1603 null /* where */, null /* selectionArgs */)); 1604 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1605 testQueryCount(Calendar.ExtendedProperties.CONTENT_URI, "event_id=" + eventId, 2); 1606 1607 // Now test deletes 1608 1609 assertEquals("delete", 1, mResolver.delete( 1610 updatedUri(attendeeUri, syncAdapter), 1611 null, null /* selectionArgs */)); 1612 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1613 testQueryCount(Calendar.Attendees.CONTENT_URI, "event_id=" + eventId, 0); 1614 1615 assertEquals("delete", 1, mResolver.delete(updatedUri(reminderUri, syncAdapter), 1616 null /* where */, null /* selectionArgs */)); 1617 1618 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1619 testQueryCount(Calendar.Reminders.CONTENT_URI, "event_id=" + eventId, 0); 1620 1621 assertEquals("delete", 1, mResolver.delete(updatedUri(alertUri, syncAdapter), 1622 null /* where */, null /* selectionArgs */)); 1623 1624 // Alerts don't dirty the event 1625 testAndClearDirty(eventId, 0); 1626 testQueryCount(Calendar.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 0); 1627 1628 assertEquals("delete", 1, mResolver.delete(updatedUri(extendedUri, syncAdapter), 1629 null /* where */, null /* selectionArgs */)); 1630 1631 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 1632 testQueryCount(Calendar.ExtendedProperties.CONTENT_URI, "event_id=" + eventId, 1); 1633 } 1634 1635 /** 1636 * Test calendar deletion 1637 * @throws Exception 1638 */ 1639 public void testCalendarDeletion() throws Exception { 1640 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1641 Uri eventUri = insertEvent(mCalendarId, findEvent("daily0")); 1642 long eventId = ContentUris.parseId(eventUri); 1643 testAndClearDirty(eventId, 1); 1644 Uri eventUri1 = insertEvent(mCalendarId, findEvent("daily1")); 1645 long eventId1 = ContentUris.parseId(eventUri); 1646 assertEquals("delete", 1, mResolver.delete(eventUri1, null, null)); 1647 // Calendar has one event and one deleted event 1648 testQueryCount(Calendar.Events.CONTENT_URI, null, 2); 1649 1650 assertEquals("delete", 1, mResolver.delete(Calendar.Calendars.CONTENT_URI, 1651 "_id=" + mCalendarId, null)); 1652 // Calendar should be deleted 1653 testQueryCount(Calendar.Calendars.CONTENT_URI, null, 0); 1654 // Event should be gone 1655 testQueryCount(Calendar.Events.CONTENT_URI, null, 0); 1656 } 1657 1658 /** 1659 * Test multiple account support. 1660 */ 1661 public void testMultipleAccounts() throws Exception { 1662 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1663 int calendarId1 = insertCal("Calendar1", DEFAULT_TIMEZONE, "user2@google.com"); 1664 Uri eventUri0 = insertEvent(mCalendarId, findEvent("daily0")); 1665 Uri eventUri1 = insertEvent(calendarId1, findEvent("daily1")); 1666 1667 testQueryCount(Calendar.Events.CONTENT_URI, null, 2); 1668 Uri eventsWithAccount = Calendar.Events.CONTENT_URI.buildUpon() 1669 .appendQueryParameter(Calendar.EventsEntity.ACCOUNT_NAME, "joe@joe.com") 1670 .appendQueryParameter(Calendar.EventsEntity.ACCOUNT_TYPE, "com.google") 1671 .build(); 1672 // Only one event for that account 1673 testQueryCount(eventsWithAccount, null, 1); 1674 1675 // Test deletion with account and selection 1676 1677 long eventId = ContentUris.parseId(eventUri1); 1678 // Wrong account, should not be deleted 1679 assertEquals("delete", 0, mResolver.delete( 1680 updatedUri(eventsWithAccount, true /* syncAdapter */), 1681 "_id=" + eventId, null /* selectionArgs */)); 1682 testQueryCount(Calendar.Events.CONTENT_URI, null, 2); 1683 // Right account, should be deleted 1684 assertEquals("delete", 1, mResolver.delete( 1685 updatedUri(Calendar.Events.CONTENT_URI, true /* syncAdapter */), 1686 "_id=" + eventId, null /* selectionArgs */)); 1687 testQueryCount(Calendar.Events.CONTENT_URI, null, 1); 1688 } 1689 1690 /** 1691 * Run commands, wiping instance table at each step. 1692 * This tests full instance expansion. 1693 * @throws Exception 1694 */ 1695 public void testCommandSequences1() throws Exception { 1696 commandSequences(true); 1697 } 1698 1699 /** 1700 * Run commands normally. 1701 * This tests incremental instance expansion. 1702 * @throws Exception 1703 */ 1704 public void testCommandSequences2() throws Exception { 1705 commandSequences(false); 1706 } 1707 1708 /** 1709 * Run thorough set of command sequences 1710 * @param wipe true if instances should be wiped and regenerated 1711 * @throws Exception 1712 */ 1713 private void commandSequences(boolean wipe) throws Exception { 1714 Cursor cursor; 1715 Uri url = null; 1716 mWipe = wipe; // Set global flag 1717 1718 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1719 1720 cursor = mResolver.query(mEventsUri, null, null, null, null); 1721 assertEquals(0, cursor.getCount()); 1722 cursor.close(); 1723 Command[] commands; 1724 1725 Log.i(TAG, "Normal insert/delete"); 1726 commands = mNormalInsertDelete; 1727 for (Command command : commands) { 1728 command.execute(); 1729 } 1730 1731 deleteAllEvents(); 1732 1733 Log.i(TAG, "All-day insert/delete"); 1734 commands = mAlldayInsertDelete; 1735 for (Command command : commands) { 1736 command.execute(); 1737 } 1738 1739 deleteAllEvents(); 1740 1741 Log.i(TAG, "Recurring insert/delete"); 1742 commands = mRecurringInsertDelete; 1743 for (Command command : commands) { 1744 command.execute(); 1745 } 1746 1747 deleteAllEvents(); 1748 1749 Log.i(TAG, "Exception with truncated recurrence"); 1750 commands = mExceptionWithTruncatedRecurrence; 1751 for (Command command : commands) { 1752 command.execute(); 1753 } 1754 1755 deleteAllEvents(); 1756 1757 Log.i(TAG, "Exception with moved recurrence"); 1758 commands = mExceptionWithMovedRecurrence; 1759 for (Command command : commands) { 1760 command.execute(); 1761 } 1762 1763 deleteAllEvents(); 1764 1765 Log.i(TAG, "Exception with cancel"); 1766 commands = mCancelInstance; 1767 for (Command command : commands) { 1768 command.execute(); 1769 } 1770 1771 deleteAllEvents(); 1772 1773 Log.i(TAG, "Exception with moved recurrence2"); 1774 commands = mExceptionWithMovedRecurrence2; 1775 for (Command command : commands) { 1776 command.execute(); 1777 } 1778 1779 deleteAllEvents(); 1780 1781 Log.i(TAG, "Exception with no recurrence"); 1782 commands = mExceptionWithNoRecurrence; 1783 for (Command command : commands) { 1784 command.execute(); 1785 } 1786 } 1787 1788 /** 1789 * Test Time toString. 1790 * @throws Exception 1791 */ 1792 // Suppressed because toString currently hangs. 1793 @Suppress 1794 public void testTimeToString() throws Exception { 1795 Time time = new Time(Time.TIMEZONE_UTC); 1796 String str = "2039-01-01T23:00:00.000Z"; 1797 String result = "20390101T230000UTC(0,0,0,-1,0)"; 1798 time.parse3339(str); 1799 assertEquals(result, time.toString()); 1800 } 1801 1802 /** 1803 * Test the query done by Event.loadEvents 1804 * Also test that instance queries work when an even straddles the expansion range 1805 * @throws Exception 1806 */ 1807 public void testInstanceQuery() throws Exception { 1808 final String[] PROJECTION = new String[] { 1809 Instances.TITLE, // 0 1810 Instances.EVENT_LOCATION, // 1 1811 Instances.ALL_DAY, // 2 1812 Instances.COLOR, // 3 1813 Instances.EVENT_TIMEZONE, // 4 1814 Instances.EVENT_ID, // 5 1815 Instances.BEGIN, // 6 1816 Instances.END, // 7 1817 Instances._ID, // 8 1818 Instances.START_DAY, // 9 1819 Instances.END_DAY, // 10 1820 Instances.START_MINUTE, // 11 1821 Instances.END_MINUTE, // 12 1822 Instances.HAS_ALARM, // 13 1823 Instances.RRULE, // 14 1824 Instances.RDATE, // 15 1825 Instances.SELF_ATTENDEE_STATUS, // 16 1826 Events.ORGANIZER, // 17 1827 Events.GUESTS_CAN_MODIFY, // 18 1828 }; 1829 1830 String orderBy = Instances.SORT_CALENDAR_VIEW; 1831 String where = Instances.SELF_ATTENDEE_STATUS + "!=" + Calendar.Attendees.ATTENDEE_STATUS_DECLINED; 1832 1833 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1834 final String START = "2008-05-01T00:00:00"; 1835 final String END = "2008-05-01T20:00:00"; 1836 1837 EventInfo[] events = { new EventInfo("normal0", 1838 START, 1839 END, 1840 false /* allDay */, 1841 DEFAULT_TIMEZONE) }; 1842 1843 insertEvent(calId, events[0]); 1844 1845 Time time = new Time(DEFAULT_TIMEZONE); 1846 time.parse3339(START); 1847 long startMs = time.toMillis(true /* ignoreDst */); 1848 // Query starting from way in the past to one hour into the event. 1849 // Query is more than 2 months so the range won't get extended by the provider. 1850 Cursor cursor = Instances.query(mResolver, PROJECTION, 1851 startMs - DateUtils.YEAR_IN_MILLIS, startMs + DateUtils.HOUR_IN_MILLIS, 1852 where, orderBy); 1853 try { 1854 assertEquals(1, cursor.getCount()); 1855 } finally { 1856 cursor.close(); 1857 } 1858 1859 // Now expand the instance range. The event overlaps the new part of the range. 1860 cursor = Instances.query(mResolver, PROJECTION, 1861 startMs - DateUtils.YEAR_IN_MILLIS, startMs + 2 * DateUtils.HOUR_IN_MILLIS, 1862 where, orderBy); 1863 try { 1864 assertEquals(1, cursor.getCount()); 1865 } finally { 1866 cursor.close(); 1867 } 1868 } 1869 1870 private Cursor queryInstances(long begin, long end) { 1871 Uri url = Uri.withAppendedPath(Calendar.Instances.CONTENT_URI, begin + "/" + end); 1872 return mResolver.query(url, null, null, null, null); 1873 } 1874 1875 protected static class MockProvider extends ContentProvider { 1876 1877 private String mAuthority; 1878 1879 private int mNumItems = 0; 1880 1881 public MockProvider(String authority) { 1882 mAuthority = authority; 1883 } 1884 1885 @Override 1886 public boolean onCreate() { 1887 return true; 1888 } 1889 1890 @Override 1891 public Cursor query(Uri uri, String[] projection, String selection, 1892 String[] selectionArgs, String sortOrder) { 1893 return new ArrayListCursor(new String[]{}, new ArrayList<ArrayList>()); 1894 } 1895 1896 @Override 1897 public String getType(Uri uri) { 1898 throw new UnsupportedOperationException(); 1899 } 1900 1901 @Override 1902 public Uri insert(Uri uri, ContentValues values) { 1903 mNumItems++; 1904 return Uri.parse("content://" + mAuthority + "/" + mNumItems); 1905 } 1906 1907 @Override 1908 public int delete(Uri uri, String selection, String[] selectionArgs) { 1909 return 0; 1910 } 1911 1912 @Override 1913 public int update(Uri uri, ContentValues values, String selection, 1914 String[] selectionArgs) { 1915 return 0; 1916 } 1917 } 1918 1919 private void cleanCalendarDataTable(SQLiteOpenHelper helper) { 1920 if (null == helper) { 1921 return; 1922 } 1923 SQLiteDatabase db = helper.getWritableDatabase(); 1924 db.execSQL("DELETE FROM CalendarCache;"); 1925 } 1926 1927 public void testGetAndSetTimezoneDatabaseVersion() throws CalendarCache.CacheException { 1928 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 1929 cleanCalendarDataTable(helper); 1930 CalendarCache cache = new CalendarCache(helper); 1931 1932 boolean hasException = false; 1933 try { 1934 String value = cache.readData(null); 1935 } catch (CalendarCache.CacheException e) { 1936 hasException = true; 1937 } 1938 assertTrue(hasException); 1939 1940 assertNull(cache.readTimezoneDatabaseVersion()); 1941 1942 cache.writeTimezoneDatabaseVersion("1234"); 1943 assertEquals("1234", cache.readTimezoneDatabaseVersion()); 1944 1945 cache.writeTimezoneDatabaseVersion("5678"); 1946 assertEquals("5678", cache.readTimezoneDatabaseVersion()); 1947 } 1948 1949 private void checkEvent(int eventId, String title, long dtStart, long dtEnd, boolean allDay) { 1950 Uri uri = Uri.parse("content://" + Calendar.AUTHORITY + "/events"); 1951 Log.i(TAG, "Looking for EventId = " + eventId); 1952 1953 Cursor cursor = mResolver.query(uri, null, null, null, null); 1954 assertEquals(1, cursor.getCount()); 1955 1956 int colIndexTitle = cursor.getColumnIndex(Calendar.Events.TITLE); 1957 int colIndexDtStart = cursor.getColumnIndex(Calendar.Events.DTSTART); 1958 int colIndexDtEnd = cursor.getColumnIndex(Calendar.Events.DTEND); 1959 int colIndexAllDay = cursor.getColumnIndex(Calendar.Events.ALL_DAY); 1960 if (!cursor.moveToNext()) { 1961 Log.e(TAG,"Could not find inserted event"); 1962 assertTrue(false); 1963 } 1964 assertEquals(title, cursor.getString(colIndexTitle)); 1965 assertEquals(dtStart, cursor.getLong(colIndexDtStart)); 1966 assertEquals(dtEnd, cursor.getLong(colIndexDtEnd)); 1967 assertEquals(allDay, (cursor.getInt(colIndexAllDay) != 0)); 1968 cursor.close(); 1969 } 1970 1971 public void testChangeTimezoneDB() throws CalendarCache.CacheException { 1972 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1973 1974 Cursor cursor = mResolver.query(Calendar.Events.CONTENT_URI, null, null, null, null); 1975 assertEquals(0, cursor.getCount()); 1976 cursor.close(); 1977 1978 EventInfo[] events = { new EventInfo("normal0", 1979 "2008-05-01T00:00:00", 1980 "2008-05-02T00:00:00", 1981 false, 1982 DEFAULT_TIMEZONE) }; 1983 1984 Uri uri = insertEvent(calId, events[0]); 1985 assertNotNull(uri); 1986 1987 // check the inserted event 1988 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 1989 1990 // inject a new time zone 1991 getProvider().doProcessEventRawTimes(TIME_ZONE_AMERICA_ANCHORAGE, 1992 MOCK_TIME_ZONE_DATABASE_VERSION); 1993 1994 // check timezone database version 1995 assertEquals(MOCK_TIME_ZONE_DATABASE_VERSION, getProvider().getTimezoneDatabaseVersion()); 1996 1997 // check that the inserted event has *not* been updated 1998 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 1999 } 2000 2001 public static final Uri PROPERTIES_CONTENT_URI = 2002 Uri.parse("content://" + Calendar.AUTHORITY + "/properties"); 2003 2004 public static final int COLUMN_KEY_INDEX = 1; 2005 public static final int COLUMN_VALUE_INDEX = 0; 2006 2007 public void testGetProviderProperties() throws CalendarCache.CacheException { 2008 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2009 cleanCalendarDataTable(helper); 2010 CalendarCache cache = new CalendarCache(helper); 2011 2012 cache.writeTimezoneDatabaseVersion("2010k"); 2013 cache.writeTimezoneInstances("America/Denver"); 2014 cache.writeTimezoneInstancesPrevious("America/Los_Angeles"); 2015 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2016 2017 Cursor cursor = mResolver.query(PROPERTIES_CONTENT_URI, null, null, null, null); 2018 assertEquals(4, cursor.getCount()); 2019 2020 assertEquals(CalendarCache.COLUMN_NAME_KEY, cursor.getColumnName(COLUMN_KEY_INDEX)); 2021 assertEquals(CalendarCache.COLUMN_NAME_VALUE, cursor.getColumnName(COLUMN_VALUE_INDEX)); 2022 2023 Map<String, String> map = new HashMap<String, String>(); 2024 2025 while (cursor.moveToNext()) { 2026 String key = cursor.getString(COLUMN_KEY_INDEX); 2027 String value = cursor.getString(COLUMN_VALUE_INDEX); 2028 map.put(key, value); 2029 } 2030 2031 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_DATABASE_VERSION)); 2032 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_TYPE)); 2033 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_INSTANCES)); 2034 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)); 2035 2036 assertEquals("2010k", map.get(CalendarCache.KEY_TIMEZONE_DATABASE_VERSION)); 2037 assertEquals("America/Denver", map.get(CalendarCache.KEY_TIMEZONE_INSTANCES)); 2038 assertEquals("America/Los_Angeles", map.get(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)); 2039 assertEquals(CalendarCache.TIMEZONE_TYPE_AUTO, map.get(CalendarCache.KEY_TIMEZONE_TYPE)); 2040 2041 cursor.close(); 2042 } 2043 2044 public void testGetProviderPropertiesByKey() throws CalendarCache.CacheException { 2045 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2046 cleanCalendarDataTable(helper); 2047 CalendarCache cache = new CalendarCache(helper); 2048 2049 cache.writeTimezoneDatabaseVersion("2010k"); 2050 cache.writeTimezoneInstances("America/Denver"); 2051 cache.writeTimezoneInstancesPrevious("America/Los_Angeles"); 2052 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2053 2054 checkValueForKey(CalendarCache.TIMEZONE_TYPE_AUTO, CalendarCache.KEY_TIMEZONE_TYPE); 2055 checkValueForKey("2010k", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2056 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES); 2057 checkValueForKey("America/Los_Angeles", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2058 } 2059 2060 private void checkValueForKey(String value, String key) { 2061 Cursor cursor = mResolver.query(PROPERTIES_CONTENT_URI, null, 2062 "key=?", new String[] {key}, null); 2063 2064 assertEquals(1, cursor.getCount()); 2065 assertTrue(cursor.moveToFirst()); 2066 assertEquals(cursor.getString(COLUMN_KEY_INDEX), key); 2067 assertEquals(cursor.getString(COLUMN_VALUE_INDEX), value); 2068 2069 cursor.close(); 2070 } 2071 2072 public void testUpdateProviderProperties() throws CalendarCache.CacheException { 2073 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2074 cleanCalendarDataTable(helper); 2075 CalendarCache cache = new CalendarCache(helper); 2076 2077 String localTimezone = TimeZone.getDefault().getID(); 2078 2079 // Set initial value 2080 cache.writeTimezoneDatabaseVersion("2010k"); 2081 2082 updateValueForKey("2009s", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2083 checkValueForKey("2009s", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2084 2085 // Set initial values 2086 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2087 cache.writeTimezoneInstances("America/Chicago"); 2088 cache.writeTimezoneInstancesPrevious("America/Denver"); 2089 2090 updateValueForKey(CalendarCache.TIMEZONE_TYPE_AUTO, CalendarCache.KEY_TIMEZONE_TYPE); 2091 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2092 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2093 2094 updateValueForKey(CalendarCache.TIMEZONE_TYPE_HOME, CalendarCache.KEY_TIMEZONE_TYPE); 2095 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES); 2096 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2097 2098 // Set initial value 2099 cache.writeTimezoneInstancesPrevious(""); 2100 updateValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2101 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2102 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2103 } 2104 2105 private void updateValueForKey(String value, String key) { 2106 ContentValues contentValues = new ContentValues(); 2107 contentValues.put(CalendarCache.COLUMN_NAME_VALUE, value); 2108 2109 int result = mResolver.update(PROPERTIES_CONTENT_URI, 2110 contentValues, 2111 CalendarCache.COLUMN_NAME_KEY + "=?", 2112 new String[] {key}); 2113 2114 assertEquals(1, result); 2115 } 2116 2117 public void testInsertOriginalTimezoneInExtProperties() throws Exception { 2118 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2119 2120 2121 EventInfo[] events = { new EventInfo("normal0", 2122 "2008-05-01T00:00:00", 2123 "2008-05-02T00:00:00", 2124 false, 2125 DEFAULT_TIMEZONE) }; 2126 2127 Uri eventUri = insertEvent(calId, events[0]); 2128 assertNotNull(eventUri); 2129 2130 long eventId = ContentUris.parseId(eventUri); 2131 assertTrue(eventId > -1); 2132 2133 // check the inserted event 2134 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 2135 2136 // Should have 1 calendars and 1 event 2137 testQueryCount(Calendar.Calendars.CONTENT_URI, null /* where */, 1); 2138 testQueryCount(Calendar.Events.CONTENT_URI, null /* where */, 1); 2139 2140 // Verify that the original timezone is correct 2141 Cursor cursor = mResolver.query(Calendar.ExtendedProperties.CONTENT_URI, 2142 null/* projection */, 2143 "event_id=" + eventId, 2144 null /* selectionArgs */, 2145 null /* sortOrder */); 2146 try { 2147 // Should have 1 extended property for the original timezone 2148 assertEquals(1, cursor.getCount()); 2149 2150 if (cursor.moveToFirst()) { 2151 long id = cursor.getLong(0); 2152 assertEquals(id, eventId); 2153 2154 assertEquals(CalendarProvider2.EXT_PROP_ORIGINAL_TIMEZONE, cursor.getString(2)); 2155 assertEquals(DEFAULT_TIMEZONE, cursor.getString(3)); 2156 } 2157 } finally { 2158 cursor.close(); 2159 } 2160 } 2161} 2162