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