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