CalendarProvider2Test.java revision 4755452ab84f704f8ce4d7e0bf61a9faeeee2b99
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.content.ComponentName; 20import android.content.ContentProvider; 21import android.content.ContentResolver; 22import android.content.ContentUris; 23import android.content.ContentValues; 24import android.content.Context; 25import android.content.Intent; 26import android.content.res.Resources; 27import android.database.Cursor; 28import android.database.MatrixCursor; 29import android.database.sqlite.SQLiteDatabase; 30import android.database.sqlite.SQLiteOpenHelper; 31import android.net.Uri; 32import android.provider.CalendarContract; 33import android.provider.CalendarContract.Calendars; 34import android.provider.CalendarContract.Colors; 35import android.provider.CalendarContract.Events; 36import android.provider.CalendarContract.Instances; 37import android.test.AndroidTestCase; 38import android.test.IsolatedContext; 39import android.test.RenamingDelegatingContext; 40import android.test.mock.MockContentResolver; 41import android.test.mock.MockContext; 42import android.test.suitebuilder.annotation.SmallTest; 43import android.test.suitebuilder.annotation.Smoke; 44import android.test.suitebuilder.annotation.Suppress; 45import android.text.TextUtils; 46import android.text.format.DateUtils; 47import android.text.format.Time; 48import android.util.Log; 49 50import java.io.File; 51import java.util.Arrays; 52import java.util.HashMap; 53import java.util.Map; 54import java.util.TimeZone; 55 56/** 57 * Runs various tests on an isolated Calendar provider with its own database. 58 * 59 * You can run the tests with the following command line: 60 * 61 * adb shell am instrument 62 * -e debug false 63 * -w 64 * -e class com.android.providers.calendar.CalendarProvider2Test 65 * com.android.providers.calendar.tests/android.test.InstrumentationTestRunner 66 * 67 * This test no longer extends ProviderTestCase2 because it actually doesn't 68 * allow you to inject a custom context (which we needed to mock out the calls 69 * to start a service). We the next best thing, which is copy the relevant code 70 * from PTC2 and extend AndroidTestCase instead. 71 */ 72// flaky test, add back to LargeTest when fixed - bug 2395696 73// @LargeTest 74public class CalendarProvider2Test extends AndroidTestCase { 75 static final String TAG = "calendar"; 76 77 private static final String DEFAULT_ACCOUNT_TYPE = "com.google"; 78 private static final String DEFAULT_ACCOUNT = "joe@joe.com"; 79 80 81 private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?"; 82 private static final String[] WHERE_CALENDARS_ARGS = { 83 "1" 84 }; 85 private static final String DEFAULT_SORT_ORDER = "begin ASC"; 86 87 private CalendarProvider2ForTesting mProvider; 88 private SQLiteDatabase mDb; 89 private MetaData mMetaData; 90 private Context mContext; 91 private MockContentResolver mResolver; 92 private Uri mEventsUri = Events.CONTENT_URI; 93 private Uri mCalendarsUri = Calendars.CONTENT_URI; 94 private int mCalendarId; 95 96 protected boolean mWipe = false; 97 protected boolean mForceDtend = false; 98 99 // We need a unique id to put in the _sync_id field so that we can create 100 // recurrence exceptions that refer to recurring events. 101 private int mGlobalSyncId = 1000; 102 private static final String CALENDAR_URL = 103 "http://www.google.com/calendar/feeds/joe%40joe.com/private/full"; 104 105 private static final String TIME_ZONE_AMERICA_ANCHORAGE = "America/Anchorage"; 106 private static final String TIME_ZONE_AMERICA_LOS_ANGELES = "America/Los_Angeles"; 107 private static final String DEFAULT_TIMEZONE = TIME_ZONE_AMERICA_LOS_ANGELES; 108 109 private static final String MOCK_TIME_ZONE_DATABASE_VERSION = "2010a"; 110 111 private static final long ONE_MINUTE_MILLIS = 60*1000; 112 private static final long ONE_HOUR_MILLIS = 3600*1000; 113 private static final long ONE_WEEK_MILLIS = 7 * 24 * 3600 * 1000; 114 115 /** 116 * We need a few more stub methods so that our tests can run 117 */ 118 protected class MockContext2 extends MockContext { 119 120 @Override 121 public String getPackageName() { 122 return getContext().getPackageName(); 123 } 124 125 @Override 126 public Resources getResources() { 127 return getContext().getResources(); 128 } 129 130 @Override 131 public File getDir(String name, int mode) { 132 // name the directory so the directory will be seperated from 133 // one created through the regular Context 134 return getContext().getDir("mockcontext2_" + name, mode); 135 } 136 137 @Override 138 public ComponentName startService(Intent service) { 139 return null; 140 } 141 142 @Override 143 public boolean stopService(Intent service) { 144 return false; 145 } 146 } 147 148 /** 149 * KeyValue is a simple class that stores a pair of strings representing 150 * a (key, value) pair. This is used for updating events. 151 */ 152 private class KeyValue { 153 String key; 154 String value; 155 156 public KeyValue(String key, String value) { 157 this.key = key; 158 this.value = value; 159 } 160 } 161 162 /** 163 * A generic command interface. This is used to support a sequence of 164 * commands that can create events, delete or update events, and then 165 * check that the state of the database is as expected. 166 */ 167 private interface Command { 168 public void execute(); 169 } 170 171 /** 172 * This is used to insert a new event into the database. The event is 173 * specified by its name (or "title"). All of the event fields (the 174 * start and end time, whether it is an all-day event, and so on) are 175 * stored in a separate table (the "mEvents" table). 176 */ 177 private class Insert implements Command { 178 EventInfo eventInfo; 179 180 public Insert(String eventName) { 181 eventInfo = findEvent(eventName); 182 } 183 184 public void execute() { 185 Log.i(TAG, "insert " + eventInfo.mTitle); 186 insertEvent(mCalendarId, eventInfo); 187 } 188 } 189 190 /** 191 * This is used to delete an event, specified by the event name. 192 */ 193 private class Delete implements Command { 194 String eventName; 195 String account; 196 String accountType; 197 int expected; 198 199 public Delete(String eventName, int expected, String account, String accountType) { 200 this.eventName = eventName; 201 this.expected = expected; 202 this.account = account; 203 this.accountType = accountType; 204 } 205 206 public void execute() { 207 Log.i(TAG, "delete " + eventName); 208 int rows = deleteMatchingEvents(eventName, account, accountType); 209 assertEquals(expected, rows); 210 } 211 } 212 213 /** 214 * This is used to update an event. The values to update are specified 215 * with an array of (key, value) pairs. Both the key and value are 216 * specified as strings. Event fields that are not really strings (such 217 * as DTSTART which is a long) should be converted to the appropriate type 218 * but that isn't supported yet. When needed, that can be added here 219 * by checking for specific keys and converting the associated values. 220 */ 221 private class Update implements Command { 222 String eventName; 223 KeyValue[] pairs; 224 225 public Update(String eventName, KeyValue[] pairs) { 226 this.eventName = eventName; 227 this.pairs = pairs; 228 } 229 230 public void execute() { 231 Log.i(TAG, "update " + eventName); 232 if (mWipe) { 233 // Wipe instance table so it will be regenerated 234 mMetaData.clearInstanceRange(); 235 } 236 ContentValues map = new ContentValues(); 237 for (KeyValue pair : pairs) { 238 String value = pair.value; 239 if (CalendarContract.Events.STATUS.equals(pair.key)) { 240 // Do type conversion for STATUS 241 map.put(pair.key, Integer.parseInt(value)); 242 } else { 243 map.put(pair.key, value); 244 } 245 } 246 if (map.size() == 1 && map.containsKey(Events.STATUS)) { 247 updateMatchingEventsStatusOnly(eventName, map); 248 } else { 249 updateMatchingEvents(eventName, map); 250 } 251 } 252 } 253 254 /** 255 * This command queries the number of events and compares it to the given 256 * expected value. 257 */ 258 private class QueryNumEvents implements Command { 259 int expected; 260 261 public QueryNumEvents(int expected) { 262 this.expected = expected; 263 } 264 265 public void execute() { 266 Cursor cursor = mResolver.query(mEventsUri, null, null, null, null); 267 assertEquals(expected, cursor.getCount()); 268 cursor.close(); 269 } 270 } 271 272 273 /** 274 * This command dumps the list of events to the log for debugging. 275 */ 276 private class DumpEvents implements Command { 277 278 public DumpEvents() { 279 } 280 281 public void execute() { 282 Cursor cursor = mResolver.query(mEventsUri, null, null, null, null); 283 dumpCursor(cursor); 284 cursor.close(); 285 } 286 } 287 288 /** 289 * This command dumps the list of instances to the log for debugging. 290 */ 291 private class DumpInstances implements Command { 292 long begin; 293 long end; 294 295 public DumpInstances(String startDate, String endDate) { 296 Time time = new Time(DEFAULT_TIMEZONE); 297 time.parse3339(startDate); 298 begin = time.toMillis(false /* use isDst */); 299 time.parse3339(endDate); 300 end = time.toMillis(false /* use isDst */); 301 } 302 303 public void execute() { 304 Cursor cursor = queryInstances(begin, end); 305 dumpCursor(cursor); 306 cursor.close(); 307 } 308 } 309 310 /** 311 * This command queries the number of instances and compares it to the given 312 * expected value. 313 */ 314 private class QueryNumInstances implements Command { 315 int expected; 316 long begin; 317 long end; 318 319 public QueryNumInstances(String startDate, String endDate, int expected) { 320 Time time = new Time(DEFAULT_TIMEZONE); 321 time.parse3339(startDate); 322 begin = time.toMillis(false /* use isDst */); 323 time.parse3339(endDate); 324 end = time.toMillis(false /* use isDst */); 325 this.expected = expected; 326 } 327 328 public void execute() { 329 Cursor cursor = queryInstances(begin, end); 330 assertEquals(expected, cursor.getCount()); 331 cursor.close(); 332 } 333 } 334 335 /** 336 * When this command runs it verifies that all of the instances in the 337 * given range match the expected instances (each instance is specified by 338 * a start date). 339 * If you just want to verify that an instance exists in a given date 340 * range, use {@link VerifyInstance} instead. 341 */ 342 private class VerifyAllInstances implements Command { 343 long[] instances; 344 long begin; 345 long end; 346 347 public VerifyAllInstances(String startDate, String endDate, String[] dates) { 348 Time time = new Time(DEFAULT_TIMEZONE); 349 time.parse3339(startDate); 350 begin = time.toMillis(false /* use isDst */); 351 time.parse3339(endDate); 352 end = time.toMillis(false /* use isDst */); 353 354 if (dates == null) { 355 return; 356 } 357 358 // Convert all the instance date strings to UTC milliseconds 359 int len = dates.length; 360 this.instances = new long[len]; 361 int index = 0; 362 for (String instance : dates) { 363 time.parse3339(instance); 364 this.instances[index++] = time.toMillis(false /* use isDst */); 365 } 366 } 367 368 public void execute() { 369 Cursor cursor = queryInstances(begin, end); 370 int len = 0; 371 if (instances != null) { 372 len = instances.length; 373 } 374 if (len != cursor.getCount()) { 375 dumpCursor(cursor); 376 } 377 assertEquals("number of instances don't match", len, cursor.getCount()); 378 379 if (instances == null) { 380 return; 381 } 382 383 int beginColumn = cursor.getColumnIndex(Instances.BEGIN); 384 while (cursor.moveToNext()) { 385 long begin = cursor.getLong(beginColumn); 386 387 // Search the list of expected instances for a matching start 388 // time. 389 boolean found = false; 390 for (long instance : instances) { 391 if (instance == begin) { 392 found = true; 393 break; 394 } 395 } 396 if (!found) { 397 int titleColumn = cursor.getColumnIndex(Events.TITLE); 398 int allDayColumn = cursor.getColumnIndex(Events.ALL_DAY); 399 400 String title = cursor.getString(titleColumn); 401 boolean allDay = cursor.getInt(allDayColumn) != 0; 402 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | 403 DateUtils.FORMAT_24HOUR; 404 if (allDay) { 405 flags |= DateUtils.FORMAT_UTC; 406 } else { 407 flags |= DateUtils.FORMAT_SHOW_TIME; 408 } 409 String date = DateUtils.formatDateRange(mContext, begin, begin, flags); 410 String mesg = String.format("Test failed!" 411 + " unexpected instance (\"%s\") at %s", 412 title, date); 413 Log.e(TAG, mesg); 414 } 415 if (!found) { 416 dumpCursor(cursor); 417 } 418 assertTrue(found); 419 } 420 cursor.close(); 421 } 422 } 423 424 /** 425 * When this command runs it verifies that the given instance exists in 426 * the given date range. 427 */ 428 private class VerifyInstance implements Command { 429 long instance; 430 boolean allDay; 431 long begin; 432 long end; 433 434 /** 435 * Creates a command to check that the given range [startDate,endDate] 436 * contains a specific instance of an event (specified by "date"). 437 * 438 * @param startDate the beginning of the date range 439 * @param endDate the end of the date range 440 * @param date the date or date-time string of an event instance 441 */ 442 public VerifyInstance(String startDate, String endDate, String date) { 443 Time time = new Time(DEFAULT_TIMEZONE); 444 time.parse3339(startDate); 445 begin = time.toMillis(false /* use isDst */); 446 time.parse3339(endDate); 447 end = time.toMillis(false /* use isDst */); 448 449 // Convert the instance date string to UTC milliseconds 450 time.parse3339(date); 451 allDay = time.allDay; 452 instance = time.toMillis(false /* use isDst */); 453 } 454 455 public void execute() { 456 Cursor cursor = queryInstances(begin, end); 457 int beginColumn = cursor.getColumnIndex(Instances.BEGIN); 458 boolean found = false; 459 while (cursor.moveToNext()) { 460 long begin = cursor.getLong(beginColumn); 461 462 if (instance == begin) { 463 found = true; 464 break; 465 } 466 } 467 if (!found) { 468 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE; 469 if (allDay) { 470 flags |= DateUtils.FORMAT_UTC; 471 } else { 472 flags |= DateUtils.FORMAT_SHOW_TIME; 473 } 474 String date = DateUtils.formatDateRange(mContext, instance, instance, flags); 475 String mesg = String.format("Test failed!" 476 + " cannot find instance at %s", 477 date); 478 Log.e(TAG, mesg); 479 } 480 assertTrue(found); 481 cursor.close(); 482 } 483 } 484 485 /** 486 * This class stores all the useful information about an event. 487 */ 488 private class EventInfo { 489 String mTitle; 490 String mDescription; 491 String mTimezone; 492 boolean mAllDay; 493 long mDtstart; 494 long mDtend; 495 String mRrule; 496 String mDuration; 497 String mOriginalTitle; 498 long mOriginalInstance; 499 int mSyncId; 500 501 // Constructor for normal events, using the default timezone 502 public EventInfo(String title, String startDate, String endDate, 503 boolean allDay) { 504 init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE); 505 } 506 507 // Constructor for normal events, specifying the timezone 508 public EventInfo(String title, String startDate, String endDate, 509 boolean allDay, String timezone) { 510 init(title, startDate, endDate, allDay, timezone); 511 } 512 513 public void init(String title, String startDate, String endDate, 514 boolean allDay, String timezone) { 515 mTitle = title; 516 Time time = new Time(); 517 if (allDay) { 518 time.timezone = Time.TIMEZONE_UTC; 519 } else if (timezone != null) { 520 time.timezone = timezone; 521 } 522 mTimezone = time.timezone; 523 time.parse3339(startDate); 524 mDtstart = time.toMillis(false /* use isDst */); 525 time.parse3339(endDate); 526 mDtend = time.toMillis(false /* use isDst */); 527 mDuration = null; 528 mRrule = null; 529 mAllDay = allDay; 530 } 531 532 // Constructor for repeating events, using the default timezone 533 public EventInfo(String title, String description, String startDate, String endDate, 534 String rrule, boolean allDay) { 535 init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE); 536 } 537 538 // Constructor for repeating events, specifying the timezone 539 public EventInfo(String title, String description, String startDate, String endDate, 540 String rrule, boolean allDay, String timezone) { 541 init(title, description, startDate, endDate, rrule, allDay, timezone); 542 } 543 544 public void init(String title, String description, String startDate, String endDate, 545 String rrule, boolean allDay, String timezone) { 546 mTitle = title; 547 mDescription = description; 548 Time time = new Time(); 549 if (allDay) { 550 time.timezone = Time.TIMEZONE_UTC; 551 } else if (timezone != null) { 552 time.timezone = timezone; 553 } 554 mTimezone = time.timezone; 555 time.parse3339(startDate); 556 mDtstart = time.toMillis(false /* use isDst */); 557 if (endDate != null) { 558 time.parse3339(endDate); 559 mDtend = time.toMillis(false /* use isDst */); 560 } 561 if (allDay) { 562 long days = 1; 563 if (endDate != null) { 564 days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS; 565 } 566 mDuration = "P" + days + "D"; 567 } else { 568 long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS; 569 mDuration = "P" + seconds + "S"; 570 } 571 mRrule = rrule; 572 mAllDay = allDay; 573 } 574 575 // Constructor for recurrence exceptions, using the default timezone 576 public EventInfo(String originalTitle, String originalInstance, String title, 577 String description, String startDate, String endDate, boolean allDay) { 578 init(originalTitle, originalInstance, 579 title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE); 580 } 581 582 public void init(String originalTitle, String originalInstance, 583 String title, String description, String startDate, String endDate, 584 boolean allDay, String timezone) { 585 mOriginalTitle = originalTitle; 586 Time time = new Time(timezone); 587 time.parse3339(originalInstance); 588 mOriginalInstance = time.toMillis(false /* use isDst */); 589 init(title, description, startDate, endDate, null /* rrule */, allDay, timezone); 590 } 591 } 592 593 private class InstanceInfo { 594 EventInfo mEvent; 595 long mBegin; 596 long mEnd; 597 int mExpectedOccurrences; 598 599 public InstanceInfo(String eventName, String startDate, String endDate, int expected) { 600 // Find the test index that contains the given event name 601 mEvent = findEvent(eventName); 602 Time time = new Time(mEvent.mTimezone); 603 time.parse3339(startDate); 604 mBegin = time.toMillis(false /* use isDst */); 605 time.parse3339(endDate); 606 mEnd = time.toMillis(false /* use isDst */); 607 mExpectedOccurrences = expected; 608 } 609 } 610 611 /** 612 * This is the main table of events. The events in this table are 613 * referred to by name in other places. 614 */ 615 private EventInfo[] mEvents = { 616 new EventInfo("normal0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", false), 617 new EventInfo("normal1", "2008-05-26T08:30:00", "2008-05-26T09:30:00", false), 618 new EventInfo("normal2", "2008-05-26T14:30:00", "2008-05-26T15:30:00", false), 619 new EventInfo("allday0", "2008-05-02T00:00:00", "2008-05-03T00:00:00", true), 620 new EventInfo("allday1", "2008-05-02T00:00:00", "2008-05-31T00:00:00", true), 621 new EventInfo("daily0", "daily from 5/1/2008 12am to 1am", 622 "2008-05-01T00:00:00", "2008-05-01T01:00:00", 623 "FREQ=DAILY;WKST=SU", false), 624 new EventInfo("daily1", "daily from 5/1/2008 8:30am to 9:30am until 5/3/2008 8am", 625 "2008-05-01T08:30:00", "2008-05-01T09:30:00", 626 "FREQ=DAILY;UNTIL=20080503T150000Z;WKST=SU", false), 627 new EventInfo("daily2", "daily from 5/1/2008 8:45am to 9:15am until 5/3/2008 10am", 628 "2008-05-01T08:45:00", "2008-05-01T09:15:00", 629 "FREQ=DAILY;UNTIL=20080503T170000Z;WKST=SU", false), 630 new EventInfo("allday daily0", "all-day daily from 5/1/2008", 631 "2008-05-01", null, 632 "FREQ=DAILY;WKST=SU", true), 633 new EventInfo("allday daily1", "all-day daily from 5/1/2008 until 5/3/2008", 634 "2008-05-01", null, 635 "FREQ=DAILY;UNTIL=20080503T000000Z;WKST=SU", true), 636 new EventInfo("allday weekly0", "all-day weekly from 5/1/2008", 637 "2008-05-01", null, 638 "FREQ=WEEKLY;WKST=SU", true), 639 new EventInfo("allday weekly1", "all-day for 2 days weekly from 5/1/2008", 640 "2008-05-01", "2008-05-03", 641 "FREQ=WEEKLY;WKST=SU", true), 642 new EventInfo("allday yearly0", "all-day yearly on 5/1/2008", 643 "2008-05-01T", null, 644 "FREQ=YEARLY;WKST=SU", true), 645 new EventInfo("weekly0", "weekly from 5/6/2008 on Tue 1pm to 2pm", 646 "2008-05-06T13:00:00", "2008-05-06T14:00:00", 647 "FREQ=WEEKLY;BYDAY=TU;WKST=MO", false), 648 new EventInfo("weekly1", "every 2 weeks from 5/6/2008 on Tue from 2:30pm to 3:30pm", 649 "2008-05-06T14:30:00", "2008-05-06T15:30:00", 650 "FREQ=WEEKLY;INTERVAL=2;BYDAY=TU;WKST=MO", false), 651 new EventInfo("monthly0", "monthly from 5/20/2008 on the 3rd Tues from 3pm to 4pm", 652 "2008-05-20T15:00:00", "2008-05-20T16:00:00", 653 "FREQ=MONTHLY;BYDAY=3TU;WKST=SU", false), 654 new EventInfo("monthly1", "monthly from 5/1/2008 on the 1st from 12:00am to 12:10am", 655 "2008-05-01T00:00:00", "2008-05-01T00:10:00", 656 "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=1", false), 657 new EventInfo("monthly2", "monthly from 5/31/2008 on the 31st 11pm to midnight", 658 "2008-05-31T23:00:00", "2008-06-01T00:00:00", 659 "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=31", false), 660 new EventInfo("daily0", "2008-05-01T00:00:00", 661 "except0", "daily0 exception for 5/1/2008 12am, change to 5/1/2008 2am to 3am", 662 "2008-05-01T02:00:00", "2008-05-01T01:03:00", false), 663 new EventInfo("daily0", "2008-05-03T00:00:00", 664 "except1", "daily0 exception for 5/3/2008 12am, change to 5/3/2008 2am to 3am", 665 "2008-05-03T02:00:00", "2008-05-03T01:03:00", false), 666 new EventInfo("daily0", "2008-05-02T00:00:00", 667 "except2", "daily0 exception for 5/2/2008 12am, change to 1/2/2008", 668 "2008-01-02T00:00:00", "2008-01-02T01:00:00", false), 669 new EventInfo("weekly0", "2008-05-13T13:00:00", 670 "except3", "daily0 exception for 5/11/2008 1pm, change to 12/11/2008 1pm", 671 "2008-12-11T13:00:00", "2008-12-11T14:00:00", false), 672 new EventInfo("weekly0", "2008-05-13T13:00:00", 673 "cancel0", "weekly0 exception for 5/13/2008 1pm", 674 "2008-05-13T13:00:00", "2008-05-13T14:00:00", false), 675 new EventInfo("yearly0", "yearly on 5/1/2008 from 1pm to 2pm", 676 "2008-05-01T13:00:00", "2008-05-01T14:00:00", 677 "FREQ=YEARLY;WKST=SU", false), 678 }; 679 680 /** 681 * This table is used to verify the events generated by mEvents. It checks that the 682 * number of instances within a given range matches the expected number 683 * of instances. 684 */ 685 private InstanceInfo[] mInstanceRanges = { 686 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-01T00:01:00", 1), 687 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-01T01:00:00", 1), 688 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 2), 689 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-02T23:59:00", 2), 690 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-02T00:01:00", 1), 691 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-02T01:00:00", 1), 692 new InstanceInfo("daily0", "2008-05-02T00:00:00", "2008-05-03T00:00:00", 2), 693 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 31), 694 new InstanceInfo("daily0", "2008-05-01T00:00:00", "2008-06-01T23:59:00", 32), 695 696 new InstanceInfo("daily1", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 1), 697 new InstanceInfo("daily1", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 2), 698 699 new InstanceInfo("daily2", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 1), 700 new InstanceInfo("daily2", "2008-05-01T00:00:00", "2008-05-31T23:59:00", 3), 701 702 new InstanceInfo("allday daily0", "2008-05-01", "2008-05-07", 7), 703 new InstanceInfo("allday daily1", "2008-05-01", "2008-05-07", 3), 704 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-07", 1), 705 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-08", 2), 706 new InstanceInfo("allday weekly0", "2008-05-01", "2008-05-31", 5), 707 new InstanceInfo("allday weekly1", "2008-05-01", "2008-05-31", 5), 708 new InstanceInfo("allday yearly0", "2008-05-01", "2009-04-30", 1), 709 new InstanceInfo("allday yearly0", "2008-05-01", "2009-05-02", 2), 710 711 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 0), 712 new InstanceInfo("weekly0", "2008-05-06T00:00:00", "2008-05-07T00:00:00", 1), 713 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 4), 714 new InstanceInfo("weekly0", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 8), 715 716 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-05-02T00:00:00", 0), 717 new InstanceInfo("weekly1", "2008-05-06T00:00:00", "2008-05-07T00:00:00", 1), 718 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 2), 719 new InstanceInfo("weekly1", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 4), 720 721 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-20T13:00:00", 0), 722 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-20T15:00:00", 1), 723 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-05-31T00:00:00", 0), 724 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-06-17T14:59:00", 0), 725 new InstanceInfo("monthly0", "2008-05-20T16:01:00", "2008-06-17T15:00:00", 1), 726 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 1), 727 new InstanceInfo("monthly0", "2008-05-01T00:00:00", "2008-06-30T00:00:00", 2), 728 729 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-05-01T01:00:00", 1), 730 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 1), 731 new InstanceInfo("monthly1", "2008-05-01T00:10:00", "2008-05-31T23:59:00", 1), 732 new InstanceInfo("monthly1", "2008-05-01T00:11:00", "2008-05-31T23:59:00", 0), 733 new InstanceInfo("monthly1", "2008-05-01T00:00:00", "2008-06-01T00:00:00", 2), 734 735 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-05-31T00:00:00", 0), 736 new InstanceInfo("monthly2", "2008-05-01T00:10:00", "2008-05-31T23:00:00", 1), 737 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-07-01T00:00:00", 1), 738 new InstanceInfo("monthly2", "2008-05-01T00:00:00", "2008-08-01T00:00:00", 2), 739 740 new InstanceInfo("yearly0", "2008-05-01", "2009-04-30", 1), 741 new InstanceInfo("yearly0", "2008-05-01", "2009-05-02", 2), 742 }; 743 744 /** 745 * This sequence of commands inserts and deletes some events. 746 */ 747 private Command[] mNormalInsertDelete = { 748 new Insert("normal0"), 749 new Insert("normal1"), 750 new Insert("normal2"), 751 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-31T00:01:00", 3), 752 new Delete("normal1", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 753 new QueryNumEvents(2), 754 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-31T00:01:00", 2), 755 new Delete("normal1", 0, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 756 new Delete("normal2", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 757 new QueryNumEvents(1), 758 new Delete("normal0", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 759 new QueryNumEvents(0), 760 }; 761 762 /** 763 * This sequence of commands inserts and deletes some all-day events. 764 */ 765 private Command[] mAlldayInsertDelete = { 766 new Insert("allday0"), 767 new Insert("allday1"), 768 new QueryNumEvents(2), 769 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-01T00:01:00", 0), 770 new QueryNumInstances("2008-05-02T00:00:00", "2008-05-02T00:01:00", 2), 771 new QueryNumInstances("2008-05-03T00:00:00", "2008-05-03T00:01:00", 1), 772 new Delete("allday0", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 773 new QueryNumEvents(1), 774 new QueryNumInstances("2008-05-02T00:00:00", "2008-05-02T00:01:00", 1), 775 new QueryNumInstances("2008-05-03T00:00:00", "2008-05-03T00:01:00", 1), 776 new Delete("allday1", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 777 new QueryNumEvents(0), 778 }; 779 780 /** 781 * This sequence of commands inserts and deletes some repeating events. 782 */ 783 private Command[] mRecurringInsertDelete = { 784 new Insert("daily0"), 785 new Insert("daily1"), 786 new QueryNumEvents(2), 787 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-02T00:01:00", 3), 788 new QueryNumInstances("2008-05-01T01:01:00", "2008-05-02T00:01:00", 2), 789 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 6), 790 new Delete("daily1", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 791 new QueryNumEvents(1), 792 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-02T00:01:00", 2), 793 new QueryNumInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 4), 794 new Delete("daily0", 1, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 795 new QueryNumEvents(0), 796 }; 797 798 /** 799 * This sequence of commands creates a recurring event with a recurrence 800 * exception that moves an event outside the expansion window. It checks that the 801 * recurrence exception does not occur in the Instances database table. 802 * Bug 1642665 803 */ 804 private Command[] mExceptionWithMovedRecurrence = { 805 new Insert("daily0"), 806 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 807 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 808 "2008-05-03T00:00:00", }), 809 new Insert("except2"), 810 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 811 new String[] {"2008-05-01T00:00:00", "2008-05-03T00:00:00"}), 812 }; 813 814 /** 815 * This sequence of commands deletes (cancels) one instance of a recurrence. 816 */ 817 private Command[] mCancelInstance = { 818 new Insert("weekly0"), 819 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-22T00:01:00", 820 new String[] {"2008-05-06T13:00:00", "2008-05-13T13:00:00", 821 "2008-05-20T13:00:00", }), 822 new Insert("cancel0"), 823 new Update("cancel0", new KeyValue[] { 824 new KeyValue(CalendarContract.Events.STATUS, 825 Integer.toString(CalendarContract.Events.STATUS_CANCELED)), 826 }), 827 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-22T00:01:00", 828 new String[] {"2008-05-06T13:00:00", 829 "2008-05-20T13:00:00", }), 830 }; 831 /** 832 * This sequence of commands creates a recurring event with a recurrence 833 * exception that moves an event from outside the expansion window into the 834 * expansion window. 835 */ 836 private Command[] mExceptionWithMovedRecurrence2 = { 837 new Insert("weekly0"), 838 new VerifyAllInstances("2008-12-01T00:00:00", "2008-12-22T00:01:00", 839 new String[] {"2008-12-02T13:00:00", "2008-12-09T13:00:00", 840 "2008-12-16T13:00:00", }), 841 new Insert("except3"), 842 new VerifyAllInstances("2008-12-01T00:00:00", "2008-12-22T00:01:00", 843 new String[] {"2008-12-02T13:00:00", "2008-12-09T13:00:00", 844 "2008-12-11T13:00:00", "2008-12-16T13:00:00", }), 845 }; 846 /** 847 * This sequence of commands creates a recurring event with a recurrence 848 * exception and then changes the end time of the recurring event. It then 849 * checks that the recurrence exception does not occur in the Instances 850 * database table. 851 */ 852 private Command[] 853 mExceptionWithTruncatedRecurrence = { 854 new Insert("daily0"), 855 // Verify 4 occurrences of the "daily0" repeating event 856 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 857 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 858 "2008-05-03T00:00:00", "2008-05-04T00:00:00"}), 859 new Insert("except1"), 860 new QueryNumEvents(2), 861 862 // Verify that one of the 4 occurrences has its start time changed 863 // so that it now matches the recurrence exception. 864 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 865 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 866 "2008-05-03T02:00:00", "2008-05-04T00:00:00"}), 867 868 // Change the end time of "daily0" but it still includes the 869 // recurrence exception. 870 new Update("daily0", new KeyValue[] { 871 new KeyValue(Events.RRULE, "FREQ=DAILY;UNTIL=20080505T150000Z;WKST=SU"), 872 }), 873 874 // Verify that the recurrence exception is still there 875 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 876 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00", 877 "2008-05-03T02:00:00", "2008-05-04T00:00:00"}), 878 // This time change the end time of "daily0" so that it excludes 879 // the recurrence exception. 880 new Update("daily0", new KeyValue[] { 881 new KeyValue(Events.RRULE, "FREQ=DAILY;UNTIL=20080502T150000Z;WKST=SU"), 882 }), 883 // The server will cancel the out-of-range exception. 884 // It would be nice for the provider to handle this automatically, 885 // but for now simulate the server-side cancel. 886 new Update("except1", new KeyValue[] { 887 new KeyValue(CalendarContract.Events.STATUS, 888 Integer.toString(CalendarContract.Events.STATUS_CANCELED)), 889 }), 890 // Verify that the recurrence exception does not appear. 891 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-04T00:01:00", 892 new String[] {"2008-05-01T00:00:00", "2008-05-02T00:00:00"}), 893 }; 894 895 /** 896 * Bug 135848. Ensure that a recurrence exception is displayed even if the recurrence 897 * is not present. 898 */ 899 private Command[] mExceptionWithNoRecurrence = { 900 new Insert("except0"), 901 new QueryNumEvents(1), 902 new VerifyAllInstances("2008-05-01T00:00:00", "2008-05-03T00:01:00", 903 new String[] {"2008-05-01T02:00:00"}), 904 }; 905 906 private EventInfo findEvent(String name) { 907 int len = mEvents.length; 908 for (int ii = 0; ii < len; ii++) { 909 EventInfo event = mEvents[ii]; 910 if (name.equals(event.mTitle)) { 911 return event; 912 } 913 } 914 return null; 915 } 916 917 @Override 918 protected void setUp() throws Exception { 919 super.setUp(); 920 // This code here is the code that was originally in ProviderTestCase2 921 mResolver = new MockContentResolver(); 922 923 final String filenamePrefix = "test."; 924 RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( 925 new MockContext2(), // The context that most methods are delegated to 926 getContext(), // The context that file methods are delegated to 927 filenamePrefix); 928 mContext = new IsolatedContext(mResolver, targetContextWrapper); 929 930 mProvider = new CalendarProvider2ForTesting(); 931 mProvider.attachInfo(mContext, null); 932 933 mResolver.addProvider(CalendarContract.AUTHORITY, mProvider); 934 mResolver.addProvider("subscribedfeeds", new MockProvider("subscribedfeeds")); 935 mResolver.addProvider("sync", new MockProvider("sync")); 936 937 mMetaData = getProvider().mMetaData; 938 mForceDtend = false; 939 940 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 941 mDb = helper.getWritableDatabase(); 942 wipeAndInitData(helper, mDb); 943 } 944 945 @Override 946 protected void tearDown() throws Exception { 947 try { 948 mDb.close(); 949 mDb = null; 950 getProvider().getDatabaseHelper().close(); 951 } catch (IllegalStateException e) { 952 e.printStackTrace(); 953 } 954 super.tearDown(); 955 } 956 957 public void wipeAndInitData(SQLiteOpenHelper helper, SQLiteDatabase db) 958 throws CalendarCache.CacheException { 959 db.beginTransaction(); 960 961 // Clean tables 962 db.delete("Calendars", null, null); 963 db.delete("Events", null, null); 964 db.delete("EventsRawTimes", null, null); 965 db.delete("Instances", null, null); 966 db.delete("CalendarMetaData", null, null); 967 db.delete("CalendarCache", null, null); 968 db.delete("Attendees", null, null); 969 db.delete("Reminders", null, null); 970 db.delete("CalendarAlerts", null, null); 971 db.delete("ExtendedProperties", null, null); 972 973 // Set CalendarCache data 974 initCalendarCacheLocked(helper, db); 975 976 // set CalendarMetaData data 977 long now = System.currentTimeMillis(); 978 ContentValues values = new ContentValues(); 979 values.put("localTimezone", "America/Los_Angeles"); 980 values.put("minInstance", 1207008000000L); // 1st April 2008 981 values.put("maxInstance", now + ONE_WEEK_MILLIS); 982 db.insert("CalendarMetaData", null, values); 983 984 db.setTransactionSuccessful(); 985 db.endTransaction(); 986 } 987 988 private void initCalendarCacheLocked(SQLiteOpenHelper helper, SQLiteDatabase db) 989 throws CalendarCache.CacheException { 990 CalendarCache cache = new CalendarCache(helper); 991 992 String localTimezone = TimeZone.getDefault().getID(); 993 994 // Set initial values 995 cache.writeDataLocked(db, CalendarCache.KEY_TIMEZONE_DATABASE_VERSION, "2010k"); 996 cache.writeDataLocked(db, CalendarCache.KEY_TIMEZONE_TYPE, CalendarCache.TIMEZONE_TYPE_AUTO); 997 cache.writeDataLocked(db, CalendarCache.KEY_TIMEZONE_INSTANCES, localTimezone); 998 cache.writeDataLocked(db, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS, localTimezone); 999 } 1000 1001 protected CalendarProvider2ForTesting getProvider() { 1002 return mProvider; 1003 } 1004 1005 /** 1006 * Dumps the contents of the given cursor to the log. For debugging. 1007 * @param cursor the database cursor 1008 */ 1009 private void dumpCursor(Cursor cursor) { 1010 cursor.moveToPosition(-1); 1011 String[] cols = cursor.getColumnNames(); 1012 1013 Log.i(TAG, "dumpCursor() count: " + cursor.getCount()); 1014 int index = 0; 1015 while (cursor.moveToNext()) { 1016 Log.i(TAG, index + " {"); 1017 for (int i = 0; i < cols.length; i++) { 1018 Log.i(TAG, " " + cols[i] + '=' + cursor.getString(i)); 1019 } 1020 Log.i(TAG, "}"); 1021 index += 1; 1022 } 1023 cursor.moveToPosition(-1); 1024 } 1025 1026 private int insertCal(String name, String timezone) { 1027 return insertCal(name, timezone, DEFAULT_ACCOUNT); 1028 } 1029 1030 /** 1031 * Creates a new calendar, with the provided name, time zone, and account name. 1032 * 1033 * @return the new calendar's _ID value 1034 */ 1035 private int insertCal(String name, String timezone, String account) { 1036 ContentValues m = new ContentValues(); 1037 m.put(Calendars.NAME, name); 1038 m.put(Calendars.CALENDAR_DISPLAY_NAME, name); 1039 m.put(Calendars.CALENDAR_COLOR, 0xff123456); 1040 m.put(Calendars.CALENDAR_TIME_ZONE, timezone); 1041 m.put(Calendars.VISIBLE, 1); 1042 m.put(Calendars.CAL_SYNC1, CALENDAR_URL); 1043 m.put(Calendars.OWNER_ACCOUNT, account); 1044 m.put(Calendars.ACCOUNT_NAME, account); 1045 m.put(Calendars.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE); 1046 m.put(Calendars.SYNC_EVENTS, 1); 1047 1048 Uri url = mResolver.insert( 1049 addSyncQueryParams(mCalendarsUri, account, DEFAULT_ACCOUNT_TYPE), m); 1050 String id = url.getLastPathSegment(); 1051 return Integer.parseInt(id); 1052 } 1053 1054 private String obsToString(Object... objs) { 1055 StringBuilder bob = new StringBuilder(); 1056 1057 for (Object obj : objs) { 1058 bob.append(obj.toString()); 1059 bob.append('#'); 1060 } 1061 1062 return bob.toString(); 1063 } 1064 1065 private long insertColor(long colorType, String colorKey, long color) { 1066 ContentValues m = new ContentValues(); 1067 m.put(Colors.ACCOUNT_NAME, DEFAULT_ACCOUNT); 1068 m.put(Colors.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE); 1069 m.put(Colors.DATA, obsToString(colorType, colorKey, color)); 1070 m.put(Colors.COLOR_TYPE, colorType); 1071 m.put(Colors.COLOR_KEY, colorKey); 1072 m.put(Colors.COLOR, color); 1073 1074 Uri uri = CalendarContract.Colors.CONTENT_URI; 1075 1076 uri = mResolver.insert(addSyncQueryParams(uri, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), m); 1077 String id = uri.getLastPathSegment(); 1078 return Long.parseLong(id); 1079 } 1080 1081 /** 1082 * Constructs a URI from a base URI (e.g. "content://com.android.calendar/calendars"), 1083 * an account name, and an account type. 1084 */ 1085 private Uri addSyncQueryParams(Uri uri, String account, String accountType) { 1086 return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") 1087 .appendQueryParameter(Calendars.ACCOUNT_NAME, account) 1088 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); 1089 } 1090 1091 private int deleteMatchingCalendars(String selection, String[] selectionArgs) { 1092 return mResolver.delete(mCalendarsUri, selection, selectionArgs); 1093 } 1094 1095 private Uri insertEvent(int calId, EventInfo event) { 1096 if (mWipe) { 1097 // Wipe instance table so it will be regenerated 1098 mMetaData.clearInstanceRange(); 1099 } 1100 ContentValues m = new ContentValues(); 1101 m.put(Events.CALENDAR_ID, calId); 1102 m.put(Events.TITLE, event.mTitle); 1103 m.put(Events.DTSTART, event.mDtstart); 1104 m.put(Events.ALL_DAY, event.mAllDay ? 1 : 0); 1105 1106 if (event.mRrule == null || mForceDtend) { 1107 // This is a normal event 1108 m.put(Events.DTEND, event.mDtend); 1109 m.remove(Events.DURATION); 1110 } 1111 if (event.mRrule != null) { 1112 // This is a repeating event 1113 m.put(Events.RRULE, event.mRrule); 1114 m.put(Events.DURATION, event.mDuration); 1115 m.remove(Events.DTEND); 1116 } 1117 1118 if (event.mDescription != null) { 1119 m.put(Events.DESCRIPTION, event.mDescription); 1120 } 1121 if (event.mTimezone != null) { 1122 m.put(Events.EVENT_TIMEZONE, event.mTimezone); 1123 } 1124 1125 if (event.mOriginalTitle != null) { 1126 // This is a recurrence exception. 1127 EventInfo recur = findEvent(event.mOriginalTitle); 1128 assertNotNull(recur); 1129 String syncId = String.format("%d", recur.mSyncId); 1130 m.put(Events.ORIGINAL_SYNC_ID, syncId); 1131 m.put(Events.ORIGINAL_ALL_DAY, recur.mAllDay ? 1 : 0); 1132 m.put(Events.ORIGINAL_INSTANCE_TIME, event.mOriginalInstance); 1133 } 1134 Uri url = mResolver.insert(mEventsUri, m); 1135 1136 // Create a fake _sync_id and add it to the event. Update the database 1137 // directly so that we don't trigger any validation checks in the 1138 // CalendarProvider. 1139 long id = ContentUris.parseId(url); 1140 mDb.execSQL("UPDATE Events SET _sync_id=" + mGlobalSyncId + " WHERE _id=" + id); 1141 event.mSyncId = mGlobalSyncId; 1142 mGlobalSyncId += 1; 1143 1144 return url; 1145 } 1146 1147 /** 1148 * Deletes all the events that match the given title. 1149 * @param title the given title to match events on 1150 * @return the number of rows deleted 1151 */ 1152 private int deleteMatchingEvents(String title, String account, String accountType) { 1153 Cursor cursor = mResolver.query(mEventsUri, new String[] { Events._ID }, 1154 "title=?", new String[] { title }, null); 1155 int numRows = 0; 1156 while (cursor.moveToNext()) { 1157 long id = cursor.getLong(0); 1158 // Do delete as a sync adapter so event is really deleted, not just marked 1159 // as deleted. 1160 Uri uri = updatedUri(ContentUris.withAppendedId(Events.CONTENT_URI, id), true, account, 1161 accountType); 1162 numRows += mResolver.delete(uri, null, null); 1163 } 1164 cursor.close(); 1165 return numRows; 1166 } 1167 1168 /** 1169 * Updates all the events that match the given title. 1170 * @param title the given title to match events on 1171 * @return the number of rows updated 1172 */ 1173 private int updateMatchingEvents(String title, ContentValues values) { 1174 String[] projection = new String[] { 1175 Events._ID, 1176 Events.DTSTART, 1177 Events.DTEND, 1178 Events.DURATION, 1179 Events.ALL_DAY, 1180 Events.RRULE, 1181 Events.EVENT_TIMEZONE, 1182 Events.ORIGINAL_SYNC_ID, 1183 }; 1184 Cursor cursor = mResolver.query(mEventsUri, projection, 1185 "title=?", new String[] { title }, null); 1186 int numRows = 0; 1187 while (cursor.moveToNext()) { 1188 long id = cursor.getLong(0); 1189 1190 // If any of the following fields are being changed, then we need 1191 // to include all of them. 1192 if (values.containsKey(Events.DTSTART) || values.containsKey(Events.DTEND) 1193 || values.containsKey(Events.DURATION) || values.containsKey(Events.ALL_DAY) 1194 || values.containsKey(Events.RRULE) 1195 || values.containsKey(Events.EVENT_TIMEZONE) 1196 || values.containsKey(CalendarContract.Events.STATUS)) { 1197 long dtstart = cursor.getLong(1); 1198 long dtend = cursor.getLong(2); 1199 String duration = cursor.getString(3); 1200 boolean allDay = cursor.getInt(4) != 0; 1201 String rrule = cursor.getString(5); 1202 String timezone = cursor.getString(6); 1203 String originalEvent = cursor.getString(7); 1204 1205 if (!values.containsKey(Events.DTSTART)) { 1206 values.put(Events.DTSTART, dtstart); 1207 } 1208 // Don't add DTEND for repeating events 1209 if (!values.containsKey(Events.DTEND) && rrule == null) { 1210 values.put(Events.DTEND, dtend); 1211 } 1212 if (!values.containsKey(Events.DURATION) && duration != null) { 1213 values.put(Events.DURATION, duration); 1214 } 1215 if (!values.containsKey(Events.ALL_DAY)) { 1216 values.put(Events.ALL_DAY, allDay ? 1 : 0); 1217 } 1218 if (!values.containsKey(Events.RRULE) && rrule != null) { 1219 values.put(Events.RRULE, rrule); 1220 } 1221 if (!values.containsKey(Events.EVENT_TIMEZONE) && timezone != null) { 1222 values.put(Events.EVENT_TIMEZONE, timezone); 1223 } 1224 if (!values.containsKey(Events.ORIGINAL_SYNC_ID) && originalEvent != null) { 1225 values.put(Events.ORIGINAL_SYNC_ID, originalEvent); 1226 } 1227 } 1228 1229 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, id); 1230 numRows += mResolver.update(uri, values, null, null); 1231 } 1232 cursor.close(); 1233 return numRows; 1234 } 1235 1236 /** 1237 * Updates the status of all the events that match the given title. 1238 * @param title the given title to match events on 1239 * @return the number of rows updated 1240 */ 1241 private int updateMatchingEventsStatusOnly(String title, ContentValues values) { 1242 String[] projection = new String[] { 1243 Events._ID, 1244 }; 1245 if (values.size() != 1 && !values.containsKey(Events.STATUS)) { 1246 return 0; 1247 } 1248 Cursor cursor = mResolver.query(mEventsUri, projection, 1249 "title=?", new String[] { title }, null); 1250 int numRows = 0; 1251 while (cursor.moveToNext()) { 1252 long id = cursor.getLong(0); 1253 1254 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, id); 1255 numRows += mResolver.update(uri, values, null, null); 1256 } 1257 cursor.close(); 1258 return numRows; 1259 } 1260 1261 1262 private void deleteAllEvents() { 1263 mDb.execSQL("DELETE FROM Events;"); 1264 mMetaData.clearInstanceRange(); 1265 } 1266 1267 public void testInsertColor() throws Exception { 1268 checkColor(Colors.TYPE_EVENT, "1" /* color key */, 11 /* color */); 1269 try { 1270 checkColor(Colors.TYPE_EVENT, "1" /* color key */, 11 /* color */); 1271 fail("Expected to fail with duplicate insertion"); 1272 } catch (IllegalArgumentException iae) { 1273 // good 1274 } 1275 1276 checkColor(Colors.TYPE_CALENDAR, "1" /* color key */, 22 /* color */); 1277 } 1278 1279 private void checkColor(long colorType, String colorKey, long color) { 1280 String[] projection = new String[] { 1281 Colors.ACCOUNT_NAME, // 0 1282 Colors.ACCOUNT_TYPE, // 1 1283 Colors.COLOR_TYPE, // 2 1284 Colors.COLOR_KEY, // 3 1285 Colors.COLOR, // 4 1286 Colors._ID, // 5 1287 Colors.DATA, // 6 1288 }; 1289 1290 long color1Id = insertColor(colorType, colorKey, color); 1291 1292 Cursor cursor = mResolver.query(Colors.CONTENT_URI, projection, Colors.COLOR_KEY 1293 + "=? AND " + Colors.COLOR_TYPE + "=?", new String[] { 1294 colorKey, Long.toString(colorType) 1295 }, null /* sortOrder */); 1296 1297 assertEquals(1, cursor.getCount()); 1298 1299 assertTrue(cursor.moveToFirst()); 1300 assertEquals(DEFAULT_ACCOUNT, cursor.getString(0)); 1301 assertEquals(DEFAULT_ACCOUNT_TYPE, cursor.getString(1)); 1302 assertEquals(colorType, cursor.getLong(2)); 1303 assertEquals(colorKey, cursor.getString(3)); 1304 assertEquals(color, cursor.getLong(4)); 1305 assertEquals(color1Id, cursor.getLong(5)); 1306 assertEquals(obsToString(colorType, colorKey, color), cursor.getString(6)); 1307 cursor.close(); 1308 } 1309 1310 public void testInsertNormalEvents() throws Exception { 1311 Cursor cursor; 1312 Uri url = null; 1313 1314 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1315 1316 cursor = mResolver.query(mEventsUri, null, null, null, null); 1317 assertEquals(0, cursor.getCount()); 1318 cursor.close(); 1319 1320 // Keep track of the number of normal events 1321 int numEvents = 0; 1322 1323 // "begin" is the earliest start time of all the normal events, 1324 // and "end" is the latest end time of all the normal events. 1325 long begin = 0, end = 0; 1326 1327 int len = mEvents.length; 1328 for (int ii = 0; ii < len; ii++) { 1329 EventInfo event = mEvents[ii]; 1330 // Skip repeating events and recurrence exceptions 1331 if (event.mRrule != null || event.mOriginalTitle != null) { 1332 continue; 1333 } 1334 if (numEvents == 0) { 1335 begin = event.mDtstart; 1336 end = event.mDtend; 1337 } else { 1338 if (begin > event.mDtstart) { 1339 begin = event.mDtstart; 1340 } 1341 if (end < event.mDtend) { 1342 end = event.mDtend; 1343 } 1344 } 1345 url = insertEvent(calId, event); 1346 numEvents += 1; 1347 } 1348 1349 // query one 1350 cursor = mResolver.query(url, null, null, null, null); 1351 assertEquals(1, cursor.getCount()); 1352 cursor.close(); 1353 1354 // query all 1355 cursor = mResolver.query(mEventsUri, null, null, null, null); 1356 assertEquals(numEvents, cursor.getCount()); 1357 cursor.close(); 1358 1359 // Check that the Instances table has one instance of each of the 1360 // normal events. 1361 cursor = queryInstances(begin, end); 1362 assertEquals(numEvents, cursor.getCount()); 1363 cursor.close(); 1364 } 1365 1366 public void testInsertRepeatingEvents() throws Exception { 1367 Cursor cursor; 1368 Uri url = null; 1369 1370 int calId = insertCal("Calendar0", "America/Los_Angeles"); 1371 1372 cursor = mResolver.query(mEventsUri, null, null, null, null); 1373 assertEquals(0, cursor.getCount()); 1374 cursor.close(); 1375 1376 // Keep track of the number of repeating events 1377 int numEvents = 0; 1378 1379 int len = mEvents.length; 1380 for (int ii = 0; ii < len; ii++) { 1381 EventInfo event = mEvents[ii]; 1382 // Skip normal events 1383 if (event.mRrule == null) { 1384 continue; 1385 } 1386 url = insertEvent(calId, event); 1387 numEvents += 1; 1388 } 1389 1390 // query one 1391 cursor = mResolver.query(url, null, null, null, null); 1392 assertEquals(1, cursor.getCount()); 1393 cursor.close(); 1394 1395 // query all 1396 cursor = mResolver.query(mEventsUri, null, null, null, null); 1397 assertEquals(numEvents, cursor.getCount()); 1398 cursor.close(); 1399 } 1400 1401 // Force a dtend value to be set and make sure instance expansion still works 1402 public void testInstanceRangeDtend() throws Exception { 1403 mForceDtend = true; 1404 testInstanceRange(); 1405 } 1406 1407 public void testInstanceRange() throws Exception { 1408 Cursor cursor; 1409 Uri url = null; 1410 1411 int calId = insertCal("Calendar0", "America/Los_Angeles"); 1412 1413 cursor = mResolver.query(mEventsUri, null, null, null, null); 1414 assertEquals(0, cursor.getCount()); 1415 cursor.close(); 1416 1417 int len = mInstanceRanges.length; 1418 for (int ii = 0; ii < len; ii++) { 1419 InstanceInfo instance = mInstanceRanges[ii]; 1420 EventInfo event = instance.mEvent; 1421 url = insertEvent(calId, event); 1422 cursor = queryInstances(instance.mBegin, instance.mEnd); 1423 if (instance.mExpectedOccurrences != cursor.getCount()) { 1424 Log.e(TAG, "Test failed! Instance index: " + ii); 1425 Log.e(TAG, "title: " + event.mTitle + " desc: " + event.mDescription 1426 + " [begin,end]: [" + instance.mBegin + " " + instance.mEnd + "]" 1427 + " expected: " + instance.mExpectedOccurrences); 1428 dumpCursor(cursor); 1429 } 1430 assertEquals(instance.mExpectedOccurrences, cursor.getCount()); 1431 cursor.close(); 1432 // Delete as sync_adapter so event is really deleted. 1433 int rows = mResolver.delete( 1434 updatedUri(url, true, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 1435 null /* selection */, null /* selection args */); 1436 assertEquals(1, rows); 1437 } 1438 } 1439 1440 public static <T> void assertArrayEquals(T[] expected, T[] actual) { 1441 if (!Arrays.equals(expected, actual)) { 1442 fail("expected:<" + Arrays.toString(expected) + 1443 "> but was:<" + Arrays.toString(actual) + ">"); 1444 } 1445 } 1446 1447 @SmallTest @Smoke 1448 public void testEscapeSearchToken() { 1449 String token = "test"; 1450 String expected = "test"; 1451 assertEquals(expected, mProvider.escapeSearchToken(token)); 1452 1453 token = "%"; 1454 expected = "#%"; 1455 assertEquals(expected, mProvider.escapeSearchToken(token)); 1456 1457 token = "_"; 1458 expected = "#_"; 1459 assertEquals(expected, mProvider.escapeSearchToken(token)); 1460 1461 token = "#"; 1462 expected = "##"; 1463 assertEquals(expected, mProvider.escapeSearchToken(token)); 1464 1465 token = "##"; 1466 expected = "####"; 1467 assertEquals(expected, mProvider.escapeSearchToken(token)); 1468 1469 token = "%_#"; 1470 expected = "#%#_##"; 1471 assertEquals(expected, mProvider.escapeSearchToken(token)); 1472 1473 token = "blah%blah"; 1474 expected = "blah#%blah"; 1475 assertEquals(expected, mProvider.escapeSearchToken(token)); 1476 } 1477 1478 @SmallTest @Smoke 1479 public void testTokenizeSearchQuery() { 1480 String query = ""; 1481 String[] expectedTokens = new String[] {}; 1482 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1483 1484 query = "a"; 1485 expectedTokens = new String[] {"a"}; 1486 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1487 1488 query = "word"; 1489 expectedTokens = new String[] {"word"}; 1490 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1491 1492 query = "two words"; 1493 expectedTokens = new String[] {"two", "words"}; 1494 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1495 1496 query = "test, punctuation."; 1497 expectedTokens = new String[] {"test", "punctuation"}; 1498 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1499 1500 query = "\"test phrase\""; 1501 expectedTokens = new String[] {"test phrase"}; 1502 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1503 1504 query = "unquoted \"this is quoted\""; 1505 expectedTokens = new String[] {"unquoted", "this is quoted"}; 1506 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1507 1508 query = " \"this is quoted\" unquoted "; 1509 expectedTokens = new String[] {"this is quoted", "unquoted"}; 1510 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1511 1512 query = "escap%e m_e"; 1513 expectedTokens = new String[] {"escap#%e", "m#_e"}; 1514 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1515 1516 query = "'a bunch' of malformed\" things"; 1517 expectedTokens = new String[] {"a", "bunch", "of", "malformed", "things"}; 1518 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1519 1520 query = "''''''....,.''trim punctuation"; 1521 expectedTokens = new String[] {"trim", "punctuation"}; 1522 assertArrayEquals(expectedTokens, mProvider.tokenizeSearchQuery(query)); 1523 } 1524 1525 @SmallTest @Smoke 1526 public void testConstructSearchWhere() { 1527 String[] tokens = new String[] {"red"}; 1528 String expected = "(title LIKE ? ESCAPE \"#\" OR " 1529 + "description LIKE ? ESCAPE \"#\" OR " 1530 + "eventLocation LIKE ? ESCAPE \"#\" OR " 1531 + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR " 1532 + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )"; 1533 assertEquals(expected, mProvider.constructSearchWhere(tokens)); 1534 1535 tokens = new String[] {}; 1536 expected = ""; 1537 assertEquals(expected, mProvider.constructSearchWhere(tokens)); 1538 1539 tokens = new String[] {"red", "green"}; 1540 expected = "(title LIKE ? ESCAPE \"#\" OR " 1541 + "description LIKE ? ESCAPE \"#\" OR " 1542 + "eventLocation LIKE ? ESCAPE \"#\" OR " 1543 + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR " 1544 + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" ) AND " 1545 + "(title LIKE ? ESCAPE \"#\" OR " 1546 + "description LIKE ? ESCAPE \"#\" OR " 1547 + "eventLocation LIKE ? ESCAPE \"#\" OR " 1548 + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR " 1549 + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )"; 1550 assertEquals(expected, mProvider.constructSearchWhere(tokens)); 1551 1552 tokens = new String[] {"red blue", "green"}; 1553 expected = "(title LIKE ? ESCAPE \"#\" OR " 1554 + "description LIKE ? ESCAPE \"#\" OR " 1555 + "eventLocation LIKE ? ESCAPE \"#\" OR " 1556 + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR " 1557 + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" ) AND " 1558 + "(title LIKE ? ESCAPE \"#\" OR " 1559 + "description LIKE ? ESCAPE \"#\" OR " 1560 + "eventLocation LIKE ? ESCAPE \"#\" OR " 1561 + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR " 1562 + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )"; 1563 assertEquals(expected, mProvider.constructSearchWhere(tokens)); 1564 } 1565 1566 @SmallTest @Smoke 1567 public void testConstructSearchArgs() { 1568 long rangeBegin = 0; 1569 long rangeEnd = 10; 1570 1571 String[] tokens = new String[] {"red"}; 1572 String[] expected = new String[] {"10", "0", "%red%", "%red%", 1573 "%red%", "%red%", "%red%" }; 1574 assertArrayEquals(expected, mProvider.constructSearchArgs(tokens, 1575 rangeBegin, rangeEnd)); 1576 1577 tokens = new String[] {"red", "blue"}; 1578 expected = new String[] { "10", "0", "%red%", "%red%", "%red%", 1579 "%red%", "%red%", "%blue%", "%blue%", 1580 "%blue%", "%blue%","%blue%"}; 1581 assertArrayEquals(expected, mProvider.constructSearchArgs(tokens, 1582 rangeBegin, rangeEnd)); 1583 1584 tokens = new String[] {}; 1585 expected = new String[] {"10", "0" }; 1586 assertArrayEquals(expected, mProvider.constructSearchArgs(tokens, 1587 rangeBegin, rangeEnd)); 1588 } 1589 1590 public void testInstanceSearchQuery() throws Exception { 1591 final String[] PROJECTION = new String[] { 1592 Instances.TITLE, // 0 1593 Instances.EVENT_LOCATION, // 1 1594 Instances.ALL_DAY, // 2 1595 Instances.CALENDAR_COLOR, // 3 1596 Instances.EVENT_TIMEZONE, // 4 1597 Instances.EVENT_ID, // 5 1598 Instances.BEGIN, // 6 1599 Instances.END, // 7 1600 Instances._ID, // 8 1601 Instances.START_DAY, // 9 1602 Instances.END_DAY, // 10 1603 Instances.START_MINUTE, // 11 1604 Instances.END_MINUTE, // 12 1605 Instances.HAS_ALARM, // 13 1606 Instances.RRULE, // 14 1607 Instances.RDATE, // 15 1608 Instances.SELF_ATTENDEE_STATUS, // 16 1609 Events.ORGANIZER, // 17 1610 Events.GUESTS_CAN_MODIFY, // 18 1611 }; 1612 1613 String orderBy = CalendarProvider2.SORT_CALENDAR_VIEW; 1614 String where = Instances.SELF_ATTENDEE_STATUS + "!=" + 1615 CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED; 1616 1617 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1618 final String START = "2008-05-01T00:00:00"; 1619 final String END = "2008-05-01T20:00:00"; 1620 1621 EventInfo event1 = new EventInfo("search orange", 1622 START, 1623 END, 1624 false /* allDay */, 1625 DEFAULT_TIMEZONE); 1626 event1.mDescription = "this is description1"; 1627 1628 EventInfo event2 = new EventInfo("search purple", 1629 START, 1630 END, 1631 false /* allDay */, 1632 DEFAULT_TIMEZONE); 1633 event2.mDescription = "lasers, out of nowhere"; 1634 1635 EventInfo event3 = new EventInfo("", 1636 START, 1637 END, 1638 false /* allDay */, 1639 DEFAULT_TIMEZONE); 1640 event3.mDescription = "kapow"; 1641 1642 EventInfo[] events = { event1, event2, event3 }; 1643 1644 insertEvent(calId, events[0]); 1645 insertEvent(calId, events[1]); 1646 insertEvent(calId, events[2]); 1647 1648 Time time = new Time(DEFAULT_TIMEZONE); 1649 time.parse3339(START); 1650 long startMs = time.toMillis(true /* ignoreDst */); 1651 // Query starting from way in the past to one hour into the event. 1652 // Query is more than 2 months so the range won't get extended by the provider. 1653 Cursor cursor = null; 1654 1655 try { 1656 cursor = queryInstances(mResolver, PROJECTION, 1657 startMs - DateUtils.YEAR_IN_MILLIS, 1658 startMs + DateUtils.HOUR_IN_MILLIS, 1659 "search", where, null, orderBy); 1660 assertEquals(2, cursor.getCount()); 1661 } finally { 1662 if (cursor != null) { 1663 cursor.close(); 1664 } 1665 } 1666 1667 try { 1668 cursor = queryInstances(mResolver, PROJECTION, 1669 startMs - DateUtils.YEAR_IN_MILLIS, 1670 startMs + DateUtils.HOUR_IN_MILLIS, 1671 "purple", where, null, orderBy); 1672 assertEquals(1, cursor.getCount()); 1673 } finally { 1674 if (cursor != null) { 1675 cursor.close(); 1676 } 1677 } 1678 1679 try { 1680 cursor = queryInstances(mResolver, PROJECTION, 1681 startMs - DateUtils.YEAR_IN_MILLIS, 1682 startMs + DateUtils.HOUR_IN_MILLIS, 1683 "puurple", where, null, orderBy); 1684 assertEquals(0, cursor.getCount()); 1685 } finally { 1686 if (cursor != null) { 1687 cursor.close(); 1688 } 1689 } 1690 1691 try { 1692 cursor = queryInstances(mResolver, PROJECTION, 1693 startMs - DateUtils.YEAR_IN_MILLIS, 1694 startMs + DateUtils.HOUR_IN_MILLIS, 1695 "purple lasers", where, null, orderBy); 1696 assertEquals(1, cursor.getCount()); 1697 } finally { 1698 if (cursor != null) { 1699 cursor.close(); 1700 } 1701 } 1702 1703 try { 1704 cursor = queryInstances(mResolver, PROJECTION, 1705 startMs - DateUtils.YEAR_IN_MILLIS, 1706 startMs + DateUtils.HOUR_IN_MILLIS, 1707 "lasers kapow", where, null, orderBy); 1708 assertEquals(0, cursor.getCount()); 1709 } finally { 1710 if (cursor != null) { 1711 cursor.close(); 1712 } 1713 } 1714 1715 try { 1716 cursor = queryInstances(mResolver, PROJECTION, 1717 startMs - DateUtils.YEAR_IN_MILLIS, 1718 startMs + DateUtils.HOUR_IN_MILLIS, 1719 "\"search purple\"", where, null, orderBy); 1720 assertEquals(1, cursor.getCount()); 1721 } finally { 1722 if (cursor != null) { 1723 cursor.close(); 1724 } 1725 } 1726 1727 try { 1728 cursor = queryInstances(mResolver, PROJECTION, 1729 startMs - DateUtils.YEAR_IN_MILLIS, 1730 startMs + DateUtils.HOUR_IN_MILLIS, 1731 "\"purple search\"", where, null, orderBy); 1732 assertEquals(0, cursor.getCount()); 1733 } finally { 1734 if (cursor != null) { 1735 cursor.close(); 1736 } 1737 } 1738 1739 try { 1740 cursor = queryInstances(mResolver, PROJECTION, 1741 startMs - DateUtils.YEAR_IN_MILLIS, 1742 startMs + DateUtils.HOUR_IN_MILLIS, 1743 "%", where, null, orderBy); 1744 assertEquals(0, cursor.getCount()); 1745 } finally { 1746 if (cursor != null) { 1747 cursor.close(); 1748 } 1749 } 1750 } 1751 1752 public void testDeleteCalendar() throws Exception { 1753 int calendarId0 = insertCal("Calendar0", DEFAULT_TIMEZONE); 1754 int calendarId1 = insertCal("Calendar1", DEFAULT_TIMEZONE, "user2@google.com"); 1755 insertEvent(calendarId0, mEvents[0]); 1756 insertEvent(calendarId1, mEvents[1]); 1757 // Should have 2 calendars and 2 events 1758 testQueryCount(CalendarContract.Calendars.CONTENT_URI, null /* where */, 2); 1759 testQueryCount(CalendarContract.Events.CONTENT_URI, null /* where */, 2); 1760 1761 int deletes = mResolver.delete(CalendarContract.Calendars.CONTENT_URI, 1762 "ownerAccount='user2@google.com'", null /* selectionArgs */); 1763 1764 assertEquals(1, deletes); 1765 // Should have 1 calendar and 1 event 1766 testQueryCount(CalendarContract.Calendars.CONTENT_URI, null /* where */, 1); 1767 testQueryCount(CalendarContract.Events.CONTENT_URI, null /* where */, 1); 1768 1769 deletes = mResolver.delete(Uri.withAppendedPath(CalendarContract.Calendars.CONTENT_URI, 1770 String.valueOf(calendarId0)), 1771 null /* selection*/ , null /* selectionArgs */); 1772 1773 assertEquals(1, deletes); 1774 // Should have 0 calendars and 0 events 1775 testQueryCount(CalendarContract.Calendars.CONTENT_URI, null /* where */, 0); 1776 testQueryCount(CalendarContract.Events.CONTENT_URI, null /* where */, 0); 1777 1778 deletes = mResolver.delete(CalendarContract.Calendars.CONTENT_URI, 1779 "ownerAccount=?", new String[] {"user2@google.com"} /* selectionArgs */); 1780 1781 assertEquals(0, deletes); 1782 } 1783 1784 public void testCalendarAlerts() throws Exception { 1785 // This projection is from AlertActivity; want to make sure it works. 1786 String[] projection = new String[] { 1787 CalendarContract.CalendarAlerts._ID, // 0 1788 CalendarContract.CalendarAlerts.TITLE, // 1 1789 CalendarContract.CalendarAlerts.EVENT_LOCATION, // 2 1790 CalendarContract.CalendarAlerts.ALL_DAY, // 3 1791 CalendarContract.CalendarAlerts.BEGIN, // 4 1792 CalendarContract.CalendarAlerts.END, // 5 1793 CalendarContract.CalendarAlerts.EVENT_ID, // 6 1794 CalendarContract.CalendarAlerts.CALENDAR_COLOR, // 7 1795 CalendarContract.CalendarAlerts.RRULE, // 8 1796 CalendarContract.CalendarAlerts.HAS_ALARM, // 9 1797 CalendarContract.CalendarAlerts.STATE, // 10 1798 CalendarContract.CalendarAlerts.ALARM_TIME, // 11 1799 }; 1800 testInsertNormalEvents(); // To initialize 1801 1802 Uri alertUri = CalendarContract.CalendarAlerts.insert(mResolver, 1 /* eventId */, 1803 2 /* begin */, 3 /* end */, 4 /* alarmTime */, 5 /* minutes */); 1804 CalendarContract.CalendarAlerts.insert(mResolver, 1 /* eventId */, 1805 2 /* begin */, 7 /* end */, 8 /* alarmTime */, 9 /* minutes */); 1806 1807 // Regular query 1808 Cursor cursor = mResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI, projection, 1809 null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1810 1811 assertEquals(2, cursor.getCount()); 1812 cursor.close(); 1813 1814 // Instance query 1815 cursor = mResolver.query(alertUri, projection, 1816 null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1817 1818 assertEquals(1, cursor.getCount()); 1819 cursor.close(); 1820 1821 // Grouped by event query 1822 cursor = mResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI_BY_INSTANCE, 1823 projection, null /* selection */, null /* selectionArgs */, null /* sortOrder */); 1824 1825 assertEquals(1, cursor.getCount()); 1826 cursor.close(); 1827 } 1828 1829 void checkEvents(int count, SQLiteDatabase db) { 1830 Cursor cursor = db.query("Events", null, null, null, null, null, null); 1831 try { 1832 assertEquals(count, cursor.getCount()); 1833 } finally { 1834 cursor.close(); 1835 } 1836 } 1837 1838 void checkEvents(int count, SQLiteDatabase db, String calendar) { 1839 Cursor cursor = db.query("Events", null, Events.CALENDAR_ID + "=?", new String[] {calendar}, 1840 null, null, null); 1841 try { 1842 assertEquals(count, cursor.getCount()); 1843 } finally { 1844 cursor.close(); 1845 } 1846 } 1847 /** 1848 * Test attendee processing 1849 * @throws Exception 1850 */ 1851 public void testAttendees() throws Exception { 1852 mCalendarId = insertCal("CalendarTestAttendees", DEFAULT_TIMEZONE); 1853 String calendarIdString = Integer.toString(mCalendarId); 1854 checkEvents(0, mDb, calendarIdString); 1855 Uri eventUri = insertEvent(mCalendarId, findEvent("daily0")); 1856 // TODO This has a race condition that causes checkEvents to not find 1857 // the just added event 1858 Thread.sleep(200); 1859 checkEvents(1, mDb, calendarIdString); 1860 long eventId = ContentUris.parseId(eventUri); 1861 1862 ContentValues attendee = new ContentValues(); 1863 attendee.put(CalendarContract.Attendees.ATTENDEE_NAME, "Joe"); 1864 attendee.put(CalendarContract.Attendees.ATTENDEE_EMAIL, DEFAULT_ACCOUNT); 1865 attendee.put(CalendarContract.Attendees.ATTENDEE_TYPE, 1866 CalendarContract.Attendees.TYPE_REQUIRED); 1867 attendee.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, 1868 CalendarContract.Attendees.RELATIONSHIP_ORGANIZER); 1869 attendee.put(CalendarContract.Attendees.EVENT_ID, eventId); 1870 Uri attendeesUri = mResolver.insert(CalendarContract.Attendees.CONTENT_URI, attendee); 1871 1872 Cursor cursor = mResolver.query(CalendarContract.Attendees.CONTENT_URI, null, 1873 "event_id=" + eventId, null, null); 1874 assertEquals("Created event is missing - cannot find EventUri = " + eventUri, 1, 1875 cursor.getCount()); 1876 cursor.close(); 1877 1878 cursor = mResolver.query(eventUri, null, null, null, null); 1879 // TODO figure out why this test fails. App works fine for this case. 1880 assertEquals("Created event is missing - cannot find EventUri = " + eventUri, 1, 1881 cursor.getCount()); 1882 int selfColumn = cursor.getColumnIndex(CalendarContract.Events.SELF_ATTENDEE_STATUS); 1883 cursor.moveToNext(); 1884 long selfAttendeeStatus = cursor.getInt(selfColumn); 1885 assertEquals(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED, selfAttendeeStatus); 1886 cursor.close(); 1887 1888 // Change status to declined 1889 attendee.put(CalendarContract.Attendees.ATTENDEE_STATUS, 1890 CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED); 1891 mResolver.update(attendeesUri, attendee, null, null); 1892 1893 cursor = mResolver.query(eventUri, null, null, null, null); 1894 cursor.moveToNext(); 1895 selfAttendeeStatus = cursor.getInt(selfColumn); 1896 assertEquals(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED, selfAttendeeStatus); 1897 cursor.close(); 1898 1899 // Add another attendee 1900 attendee.put(CalendarContract.Attendees.ATTENDEE_NAME, "Dude"); 1901 attendee.put(CalendarContract.Attendees.ATTENDEE_EMAIL, "dude@dude.com"); 1902 attendee.put(CalendarContract.Attendees.ATTENDEE_STATUS, 1903 CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED); 1904 mResolver.insert(CalendarContract.Attendees.CONTENT_URI, attendee); 1905 1906 cursor = mResolver.query(CalendarContract.Attendees.CONTENT_URI, null, 1907 "event_id=" + mCalendarId, null, null); 1908 assertEquals(2, cursor.getCount()); 1909 cursor.close(); 1910 1911 cursor = mResolver.query(eventUri, null, null, null, null); 1912 cursor.moveToNext(); 1913 selfAttendeeStatus = cursor.getInt(selfColumn); 1914 assertEquals(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED, selfAttendeeStatus); 1915 cursor.close(); 1916 } 1917 1918 /** 1919 * Test the event's dirty status and clear it. 1920 * 1921 * @param eventId event to fetch. 1922 * @param wanted the wanted dirty status 1923 */ 1924 private void testAndClearDirty(long eventId, int wanted) { 1925 Cursor cursor = mResolver.query( 1926 ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId), 1927 null, null, null, null); 1928 try { 1929 assertEquals("Event count", 1, cursor.getCount()); 1930 cursor.moveToNext(); 1931 int dirty = cursor.getInt(cursor.getColumnIndex(CalendarContract.Events.DIRTY)); 1932 assertEquals("dirty flag", wanted, dirty); 1933 if (dirty == 1) { 1934 // Have to access database directly since provider will set dirty again. 1935 mDb.execSQL("UPDATE Events SET " + Events.DIRTY + "=0 WHERE _id=" + eventId); 1936 } 1937 } finally { 1938 cursor.close(); 1939 } 1940 } 1941 1942 /** 1943 * Test the count of results from a query. 1944 * @param uri The URI to query 1945 * @param where The where string or null. 1946 * @param wanted The number of results wanted. An assertion is thrown if it doesn't match. 1947 */ 1948 private void testQueryCount(Uri uri, String where, int wanted) { 1949 Cursor cursor = mResolver.query(uri, null/* projection */, where, null /* selectionArgs */, 1950 null /* sortOrder */); 1951 try { 1952 assertEquals("query results", wanted, cursor.getCount()); 1953 } finally { 1954 cursor.close(); 1955 } 1956 } 1957 1958 /** 1959 * Test dirty flag processing. 1960 * @throws Exception 1961 */ 1962 public void testDirty() throws Exception { 1963 internalTestDirty(false); 1964 } 1965 1966 /** 1967 * Test dirty flag processing for updates from a sync adapter. 1968 * @throws Exception 1969 */ 1970 public void testDirtyWithSyncAdapter() throws Exception { 1971 internalTestDirty(true); 1972 } 1973 1974 /** 1975 * Adds CALLER_IS_SYNCADAPTER to URI if this is a sync adapter operation. Otherwise, 1976 * returns the original URI. 1977 */ 1978 private Uri updatedUri(Uri uri, boolean syncAdapter, String account, String accountType) { 1979 if (syncAdapter) { 1980 return addSyncQueryParams(uri, account, accountType); 1981 } else { 1982 return uri; 1983 } 1984 } 1985 1986 /** 1987 * Test dirty flag processing either for syncAdapter operations or client operations. 1988 * The main difference is syncAdapter operations don't set the dirty bit. 1989 */ 1990 private void internalTestDirty(boolean syncAdapter) throws Exception { 1991 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 1992 1993 long now = System.currentTimeMillis(); 1994 long begin = (now / 1000) * 1000; 1995 long end = begin + ONE_HOUR_MILLIS; 1996 Time time = new Time(DEFAULT_TIMEZONE); 1997 time.set(begin); 1998 String startDate = time.format3339(false); 1999 time.set(end); 2000 String endDate = time.format3339(false); 2001 2002 EventInfo eventInfo = new EventInfo("current", startDate, endDate, false); 2003 Uri eventUri = insertEvent(mCalendarId, eventInfo); 2004 2005 long eventId = ContentUris.parseId(eventUri); 2006 testAndClearDirty(eventId, 1); 2007 2008 ContentValues attendee = new ContentValues(); 2009 attendee.put(CalendarContract.Attendees.ATTENDEE_NAME, "Joe"); 2010 attendee.put(CalendarContract.Attendees.ATTENDEE_EMAIL, DEFAULT_ACCOUNT); 2011 attendee.put(CalendarContract.Attendees.ATTENDEE_TYPE, 2012 CalendarContract.Attendees.TYPE_REQUIRED); 2013 attendee.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, 2014 CalendarContract.Attendees.RELATIONSHIP_ORGANIZER); 2015 attendee.put(CalendarContract.Attendees.EVENT_ID, eventId); 2016 2017 Uri attendeeUri = mResolver.insert( 2018 updatedUri(CalendarContract.Attendees.CONTENT_URI, syncAdapter, DEFAULT_ACCOUNT, 2019 DEFAULT_ACCOUNT_TYPE), 2020 attendee); 2021 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2022 testQueryCount(CalendarContract.Attendees.CONTENT_URI, "event_id=" + eventId, 1); 2023 2024 ContentValues reminder = new ContentValues(); 2025 reminder.put(CalendarContract.Reminders.MINUTES, 30); 2026 reminder.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_EMAIL); 2027 reminder.put(CalendarContract.Attendees.EVENT_ID, eventId); 2028 2029 Uri reminderUri = mResolver.insert( 2030 updatedUri(CalendarContract.Reminders.CONTENT_URI, syncAdapter, DEFAULT_ACCOUNT, 2031 DEFAULT_ACCOUNT_TYPE), reminder); 2032 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2033 testQueryCount(CalendarContract.Reminders.CONTENT_URI, "event_id=" + eventId, 1); 2034 2035 long alarmTime = begin + 5 * ONE_MINUTE_MILLIS; 2036 2037 ContentValues alert = new ContentValues(); 2038 alert.put(CalendarContract.CalendarAlerts.BEGIN, begin); 2039 alert.put(CalendarContract.CalendarAlerts.END, end); 2040 alert.put(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime); 2041 alert.put(CalendarContract.CalendarAlerts.CREATION_TIME, now); 2042 alert.put(CalendarContract.CalendarAlerts.RECEIVED_TIME, now); 2043 alert.put(CalendarContract.CalendarAlerts.NOTIFY_TIME, now); 2044 alert.put(CalendarContract.CalendarAlerts.STATE, 2045 CalendarContract.CalendarAlerts.STATE_SCHEDULED); 2046 alert.put(CalendarContract.CalendarAlerts.MINUTES, 30); 2047 alert.put(CalendarContract.CalendarAlerts.EVENT_ID, eventId); 2048 2049 Uri alertUri = mResolver.insert( 2050 updatedUri(CalendarContract.CalendarAlerts.CONTENT_URI, syncAdapter, 2051 DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), alert); 2052 // Alerts don't dirty the event 2053 testAndClearDirty(eventId, 0); 2054 testQueryCount(CalendarContract.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 1); 2055 2056 ContentValues extended = new ContentValues(); 2057 extended.put(CalendarContract.ExtendedProperties.NAME, "foo"); 2058 extended.put(CalendarContract.ExtendedProperties.VALUE, "bar"); 2059 extended.put(CalendarContract.ExtendedProperties.EVENT_ID, eventId); 2060 2061 Uri extendedUri = null; 2062 if (syncAdapter) { 2063 // Only the sync adapter is allowed to modify ExtendedProperties. 2064 extendedUri = mResolver.insert( 2065 updatedUri(CalendarContract.ExtendedProperties.CONTENT_URI, syncAdapter, 2066 DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), extended); 2067 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2068 testQueryCount(CalendarContract.ExtendedProperties.CONTENT_URI, 2069 "event_id=" + eventId, 2); 2070 } else { 2071 // Confirm that inserting as app fails. 2072 try { 2073 extendedUri = mResolver.insert( 2074 updatedUri(CalendarContract.ExtendedProperties.CONTENT_URI, syncAdapter, 2075 DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), extended); 2076 fail("Only sync adapter should be allowed to insert into ExtendedProperties"); 2077 } catch (IllegalArgumentException iae) {} 2078 } 2079 2080 // Now test updates 2081 2082 attendee = new ContentValues(); 2083 attendee.put(CalendarContract.Attendees.ATTENDEE_NAME, "Sam"); 2084 2085 assertEquals("update", 1, mResolver.update( 2086 updatedUri(attendeeUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2087 attendee, 2088 null /* where */, null /* selectionArgs */)); 2089 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2090 2091 testQueryCount(CalendarContract.Attendees.CONTENT_URI, "event_id=" + eventId, 1); 2092 2093 alert = new ContentValues(); 2094 alert.put(CalendarContract.CalendarAlerts.STATE, 2095 CalendarContract.CalendarAlerts.STATE_DISMISSED); 2096 2097 assertEquals("update", 1, mResolver.update( 2098 updatedUri(alertUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), alert, 2099 null /* where */, null /* selectionArgs */)); 2100 // Alerts don't dirty the event 2101 testAndClearDirty(eventId, 0); 2102 testQueryCount(CalendarContract.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 1); 2103 2104 extended = new ContentValues(); 2105 extended.put(CalendarContract.ExtendedProperties.VALUE, "baz"); 2106 2107 if (syncAdapter) { 2108 assertEquals("update", 1, mResolver.update( 2109 updatedUri(extendedUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2110 extended, 2111 null /* where */, null /* selectionArgs */)); 2112 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2113 testQueryCount(CalendarContract.ExtendedProperties.CONTENT_URI, 2114 "event_id=" + eventId, 2); 2115 } 2116 2117 // Now test deletes 2118 2119 assertEquals("delete", 1, mResolver.delete( 2120 updatedUri(attendeeUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2121 null, null /* selectionArgs */)); 2122 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2123 testQueryCount(CalendarContract.Attendees.CONTENT_URI, "event_id=" + eventId, 0); 2124 2125 assertEquals("delete", 1, mResolver.delete( 2126 updatedUri(reminderUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2127 null /* where */, null /* selectionArgs */)); 2128 2129 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2130 testQueryCount(CalendarContract.Reminders.CONTENT_URI, "event_id=" + eventId, 0); 2131 2132 assertEquals("delete", 1, mResolver.delete( 2133 updatedUri(alertUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2134 null /* where */, null /* selectionArgs */)); 2135 2136 // Alerts don't dirty the event 2137 testAndClearDirty(eventId, 0); 2138 testQueryCount(CalendarContract.CalendarAlerts.CONTENT_URI, "event_id=" + eventId, 0); 2139 2140 if (syncAdapter) { 2141 assertEquals("delete", 1, mResolver.delete( 2142 updatedUri(extendedUri, syncAdapter, DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), 2143 null /* where */, null /* selectionArgs */)); 2144 2145 testAndClearDirty(eventId, syncAdapter ? 0 : 1); 2146 testQueryCount(CalendarContract.ExtendedProperties.CONTENT_URI, "event_id=" + eventId, 1); 2147 } 2148 } 2149 2150 /** 2151 * Test calendar deletion 2152 * @throws Exception 2153 */ 2154 public void testCalendarDeletion() throws Exception { 2155 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2156 Uri eventUri = insertEvent(mCalendarId, findEvent("daily0")); 2157 long eventId = ContentUris.parseId(eventUri); 2158 testAndClearDirty(eventId, 1); 2159 Uri eventUri1 = insertEvent(mCalendarId, findEvent("daily1")); 2160 long eventId1 = ContentUris.parseId(eventUri); 2161 assertEquals("delete", 1, mResolver.delete(eventUri1, null, null)); 2162 // Calendar has one event and one deleted event 2163 testQueryCount(CalendarContract.Events.CONTENT_URI, null, 2); 2164 2165 assertEquals("delete", 1, mResolver.delete(CalendarContract.Calendars.CONTENT_URI, 2166 "_id=" + mCalendarId, null)); 2167 // Calendar should be deleted 2168 testQueryCount(CalendarContract.Calendars.CONTENT_URI, null, 0); 2169 // Event should be gone 2170 testQueryCount(CalendarContract.Events.CONTENT_URI, null, 0); 2171 } 2172 2173 /** 2174 * Test multiple account support. 2175 */ 2176 public void testMultipleAccounts() throws Exception { 2177 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2178 int calendarId1 = insertCal("Calendar1", DEFAULT_TIMEZONE, "user2@google.com"); 2179 Uri eventUri0 = insertEvent(mCalendarId, findEvent("daily0")); 2180 Uri eventUri1 = insertEvent(calendarId1, findEvent("daily1")); 2181 2182 testQueryCount(CalendarContract.Events.CONTENT_URI, null, 2); 2183 Uri eventsWithAccount = CalendarContract.Events.CONTENT_URI.buildUpon() 2184 .appendQueryParameter(CalendarContract.EventsEntity.ACCOUNT_NAME, DEFAULT_ACCOUNT) 2185 .appendQueryParameter(CalendarContract.EventsEntity.ACCOUNT_TYPE, 2186 DEFAULT_ACCOUNT_TYPE) 2187 .build(); 2188 // Only one event for that account 2189 testQueryCount(eventsWithAccount, null, 1); 2190 2191 // Test deletion with account and selection 2192 2193 long eventId = ContentUris.parseId(eventUri1); 2194 // Wrong account, should not be deleted 2195 assertEquals("delete", 0, mResolver.delete( 2196 updatedUri(eventsWithAccount, true /* syncAdapter */, DEFAULT_ACCOUNT, 2197 DEFAULT_ACCOUNT_TYPE), 2198 "_id=" + eventId, null /* selectionArgs */)); 2199 testQueryCount(CalendarContract.Events.CONTENT_URI, null, 2); 2200 // Right account, should be deleted 2201 assertEquals("delete", 1, mResolver.delete( 2202 updatedUri(CalendarContract.Events.CONTENT_URI, true /* syncAdapter */, 2203 "user2@google.com", DEFAULT_ACCOUNT_TYPE), 2204 "_id=" + eventId, null /* selectionArgs */)); 2205 testQueryCount(CalendarContract.Events.CONTENT_URI, null, 1); 2206 } 2207 2208 /** 2209 * Run commands, wiping instance table at each step. 2210 * This tests full instance expansion. 2211 * @throws Exception 2212 */ 2213 public void testCommandSequences1() throws Exception { 2214 commandSequences(true); 2215 } 2216 2217 /** 2218 * Run commands normally. 2219 * This tests incremental instance expansion. 2220 * @throws Exception 2221 */ 2222 public void testCommandSequences2() throws Exception { 2223 commandSequences(false); 2224 } 2225 2226 /** 2227 * Run thorough set of command sequences 2228 * @param wipe true if instances should be wiped and regenerated 2229 * @throws Exception 2230 */ 2231 private void commandSequences(boolean wipe) throws Exception { 2232 Cursor cursor; 2233 Uri url = null; 2234 mWipe = wipe; // Set global flag 2235 2236 mCalendarId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2237 2238 cursor = mResolver.query(mEventsUri, null, null, null, null); 2239 assertEquals(0, cursor.getCount()); 2240 cursor.close(); 2241 Command[] commands; 2242 2243 Log.i(TAG, "Normal insert/delete"); 2244 commands = mNormalInsertDelete; 2245 for (Command command : commands) { 2246 command.execute(); 2247 } 2248 2249 deleteAllEvents(); 2250 2251 Log.i(TAG, "All-day insert/delete"); 2252 commands = mAlldayInsertDelete; 2253 for (Command command : commands) { 2254 command.execute(); 2255 } 2256 2257 deleteAllEvents(); 2258 2259 Log.i(TAG, "Recurring insert/delete"); 2260 commands = mRecurringInsertDelete; 2261 for (Command command : commands) { 2262 command.execute(); 2263 } 2264 2265 deleteAllEvents(); 2266 2267 Log.i(TAG, "Exception with truncated recurrence"); 2268 commands = mExceptionWithTruncatedRecurrence; 2269 for (Command command : commands) { 2270 command.execute(); 2271 } 2272 2273 deleteAllEvents(); 2274 2275 Log.i(TAG, "Exception with moved recurrence"); 2276 commands = mExceptionWithMovedRecurrence; 2277 for (Command command : commands) { 2278 command.execute(); 2279 } 2280 2281 deleteAllEvents(); 2282 2283 Log.i(TAG, "Exception with cancel"); 2284 commands = mCancelInstance; 2285 for (Command command : commands) { 2286 command.execute(); 2287 } 2288 2289 deleteAllEvents(); 2290 2291 Log.i(TAG, "Exception with moved recurrence2"); 2292 commands = mExceptionWithMovedRecurrence2; 2293 for (Command command : commands) { 2294 command.execute(); 2295 } 2296 2297 deleteAllEvents(); 2298 2299 Log.i(TAG, "Exception with no recurrence"); 2300 commands = mExceptionWithNoRecurrence; 2301 for (Command command : commands) { 2302 command.execute(); 2303 } 2304 } 2305 2306 /** 2307 * Test Time toString. 2308 * @throws Exception 2309 */ 2310 // Suppressed because toString currently hangs. 2311 @Suppress 2312 public void testTimeToString() throws Exception { 2313 Time time = new Time(Time.TIMEZONE_UTC); 2314 String str = "2039-01-01T23:00:00.000Z"; 2315 String result = "20390101T230000UTC(0,0,0,-1,0)"; 2316 time.parse3339(str); 2317 assertEquals(result, time.toString()); 2318 } 2319 2320 /** 2321 * Test the query done by Event.loadEvents 2322 * Also test that instance queries work when an event straddles the expansion range 2323 * @throws Exception 2324 */ 2325 public void testInstanceQuery() throws Exception { 2326 final String[] PROJECTION = new String[] { 2327 Instances.TITLE, // 0 2328 Instances.EVENT_LOCATION, // 1 2329 Instances.ALL_DAY, // 2 2330 Instances.CALENDAR_COLOR, // 3 2331 Instances.EVENT_TIMEZONE, // 4 2332 Instances.EVENT_ID, // 5 2333 Instances.BEGIN, // 6 2334 Instances.END, // 7 2335 Instances._ID, // 8 2336 Instances.START_DAY, // 9 2337 Instances.END_DAY, // 10 2338 Instances.START_MINUTE, // 11 2339 Instances.END_MINUTE, // 12 2340 Instances.HAS_ALARM, // 13 2341 Instances.RRULE, // 14 2342 Instances.RDATE, // 15 2343 Instances.SELF_ATTENDEE_STATUS, // 16 2344 Events.ORGANIZER, // 17 2345 Events.GUESTS_CAN_MODIFY, // 18 2346 }; 2347 2348 String orderBy = CalendarProvider2.SORT_CALENDAR_VIEW; 2349 String where = Instances.SELF_ATTENDEE_STATUS + "!=" 2350 + CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED; 2351 2352 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2353 final String START = "2008-05-01T00:00:00"; 2354 final String END = "2008-05-01T20:00:00"; 2355 2356 EventInfo[] events = { new EventInfo("normal0", 2357 START, 2358 END, 2359 false /* allDay */, 2360 DEFAULT_TIMEZONE) }; 2361 2362 insertEvent(calId, events[0]); 2363 2364 Time time = new Time(DEFAULT_TIMEZONE); 2365 time.parse3339(START); 2366 long startMs = time.toMillis(true /* ignoreDst */); 2367 // Query starting from way in the past to one hour into the event. 2368 // Query is more than 2 months so the range won't get extended by the provider. 2369 Cursor cursor = queryInstances(mResolver, PROJECTION, 2370 startMs - DateUtils.YEAR_IN_MILLIS, startMs + DateUtils.HOUR_IN_MILLIS, 2371 where, null, orderBy); 2372 try { 2373 assertEquals(1, cursor.getCount()); 2374 } finally { 2375 cursor.close(); 2376 } 2377 2378 // Now expand the instance range. The event overlaps the new part of the range. 2379 cursor = queryInstances(mResolver, PROJECTION, 2380 startMs - DateUtils.YEAR_IN_MILLIS, startMs + 2 * DateUtils.HOUR_IN_MILLIS, 2381 where, null, orderBy); 2382 try { 2383 assertEquals(1, cursor.getCount()); 2384 } finally { 2385 cursor.close(); 2386 } 2387 } 2388 2389 /** 2390 * Performs a query to return all visible instances in the given range that 2391 * match the given selection. This is a blocking function and should not be 2392 * done on the UI thread. This will cause an expansion of recurring events 2393 * to fill this time range if they are not already expanded and will slow 2394 * down for larger time ranges with many recurring events. 2395 * 2396 * @param cr The ContentResolver to use for the query 2397 * @param projection The columns to return 2398 * @param begin The start of the time range to query in UTC millis since 2399 * epoch 2400 * @param end The end of the time range to query in UTC millis since epoch 2401 * @param selection Filter on the query as an SQL WHERE statement 2402 * @param selectionArgs Args to replace any '?'s in the selection 2403 * @param orderBy How to order the rows as an SQL ORDER BY statement 2404 * @return A Cursor of instances matching the selection 2405 */ 2406 private static final Cursor queryInstances(ContentResolver cr, String[] projection, long begin, 2407 long end, String selection, String[] selectionArgs, String orderBy) { 2408 2409 Uri.Builder builder = Instances.CONTENT_URI.buildUpon(); 2410 ContentUris.appendId(builder, begin); 2411 ContentUris.appendId(builder, end); 2412 if (TextUtils.isEmpty(selection)) { 2413 selection = WHERE_CALENDARS_SELECTED; 2414 selectionArgs = WHERE_CALENDARS_ARGS; 2415 } else { 2416 selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED; 2417 if (selectionArgs != null && selectionArgs.length > 0) { 2418 selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1); 2419 selectionArgs[selectionArgs.length - 1] = WHERE_CALENDARS_ARGS[0]; 2420 } else { 2421 selectionArgs = WHERE_CALENDARS_ARGS; 2422 } 2423 } 2424 return cr.query(builder.build(), projection, selection, selectionArgs, 2425 orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2426 } 2427 2428 /** 2429 * Performs a query to return all visible instances in the given range that 2430 * match the given selection. This is a blocking function and should not be 2431 * done on the UI thread. This will cause an expansion of recurring events 2432 * to fill this time range if they are not already expanded and will slow 2433 * down for larger time ranges with many recurring events. 2434 * 2435 * @param cr The ContentResolver to use for the query 2436 * @param projection The columns to return 2437 * @param begin The start of the time range to query in UTC millis since 2438 * epoch 2439 * @param end The end of the time range to query in UTC millis since epoch 2440 * @param searchQuery A string of space separated search terms. Segments 2441 * enclosed by double quotes will be treated as a single term. 2442 * @param selection Filter on the query as an SQL WHERE statement 2443 * @param selectionArgs Args to replace any '?'s in the selection 2444 * @param orderBy How to order the rows as an SQL ORDER BY statement 2445 * @return A Cursor of instances matching the selection 2446 */ 2447 public static final Cursor queryInstances(ContentResolver cr, String[] projection, long begin, 2448 long end, String searchQuery, String selection, String[] selectionArgs, String orderBy) 2449 { 2450 Uri.Builder builder = Instances.CONTENT_SEARCH_URI.buildUpon(); 2451 ContentUris.appendId(builder, begin); 2452 ContentUris.appendId(builder, end); 2453 builder = builder.appendPath(searchQuery); 2454 if (TextUtils.isEmpty(selection)) { 2455 selection = WHERE_CALENDARS_SELECTED; 2456 selectionArgs = WHERE_CALENDARS_ARGS; 2457 } else { 2458 selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED; 2459 if (selectionArgs != null && selectionArgs.length > 0) { 2460 selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1); 2461 selectionArgs[selectionArgs.length - 1] = WHERE_CALENDARS_ARGS[0]; 2462 } else { 2463 selectionArgs = WHERE_CALENDARS_ARGS; 2464 } 2465 } 2466 return cr.query(builder.build(), projection, selection, selectionArgs, 2467 orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2468 } 2469 2470 private Cursor queryInstances(long begin, long end) { 2471 Uri url = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI, begin + "/" + end); 2472 return mResolver.query(url, null, null, null, null); 2473 } 2474 2475 protected static class MockProvider extends ContentProvider { 2476 2477 private String mAuthority; 2478 2479 private int mNumItems = 0; 2480 2481 public MockProvider(String authority) { 2482 mAuthority = authority; 2483 } 2484 2485 @Override 2486 public boolean onCreate() { 2487 return true; 2488 } 2489 2490 @Override 2491 public Cursor query(Uri uri, String[] projection, String selection, 2492 String[] selectionArgs, String sortOrder) { 2493 return new MatrixCursor(new String[]{ "_id" }, 0); 2494 } 2495 2496 @Override 2497 public String getType(Uri uri) { 2498 throw new UnsupportedOperationException(); 2499 } 2500 2501 @Override 2502 public Uri insert(Uri uri, ContentValues values) { 2503 mNumItems++; 2504 return Uri.parse("content://" + mAuthority + "/" + mNumItems); 2505 } 2506 2507 @Override 2508 public int delete(Uri uri, String selection, String[] selectionArgs) { 2509 return 0; 2510 } 2511 2512 @Override 2513 public int update(Uri uri, ContentValues values, String selection, 2514 String[] selectionArgs) { 2515 return 0; 2516 } 2517 } 2518 2519 private void cleanCalendarDataTable(SQLiteOpenHelper helper) { 2520 if (null == helper) { 2521 return; 2522 } 2523 SQLiteDatabase db = helper.getWritableDatabase(); 2524 db.execSQL("DELETE FROM CalendarCache;"); 2525 } 2526 2527 public void testGetAndSetTimezoneDatabaseVersion() throws CalendarCache.CacheException { 2528 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2529 cleanCalendarDataTable(helper); 2530 CalendarCache cache = new CalendarCache(helper); 2531 2532 boolean hasException = false; 2533 try { 2534 String value = cache.readData(null); 2535 } catch (CalendarCache.CacheException e) { 2536 hasException = true; 2537 } 2538 assertTrue(hasException); 2539 2540 assertNull(cache.readTimezoneDatabaseVersion()); 2541 2542 cache.writeTimezoneDatabaseVersion("1234"); 2543 assertEquals("1234", cache.readTimezoneDatabaseVersion()); 2544 2545 cache.writeTimezoneDatabaseVersion("5678"); 2546 assertEquals("5678", cache.readTimezoneDatabaseVersion()); 2547 } 2548 2549 private void checkEvent(int eventId, String title, long dtStart, long dtEnd, boolean allDay) { 2550 Uri uri = Uri.parse("content://" + CalendarContract.AUTHORITY + "/events"); 2551 Log.i(TAG, "Looking for EventId = " + eventId); 2552 2553 Cursor cursor = mResolver.query(uri, null, null, null, null); 2554 assertEquals(1, cursor.getCount()); 2555 2556 int colIndexTitle = cursor.getColumnIndex(CalendarContract.Events.TITLE); 2557 int colIndexDtStart = cursor.getColumnIndex(CalendarContract.Events.DTSTART); 2558 int colIndexDtEnd = cursor.getColumnIndex(CalendarContract.Events.DTEND); 2559 int colIndexAllDay = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY); 2560 if (!cursor.moveToNext()) { 2561 Log.e(TAG,"Could not find inserted event"); 2562 assertTrue(false); 2563 } 2564 assertEquals(title, cursor.getString(colIndexTitle)); 2565 assertEquals(dtStart, cursor.getLong(colIndexDtStart)); 2566 assertEquals(dtEnd, cursor.getLong(colIndexDtEnd)); 2567 assertEquals(allDay, (cursor.getInt(colIndexAllDay) != 0)); 2568 cursor.close(); 2569 } 2570 2571 public void testChangeTimezoneDB() { 2572 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2573 2574 Cursor cursor = mResolver 2575 .query(CalendarContract.Events.CONTENT_URI, null, null, null, null); 2576 assertEquals(0, cursor.getCount()); 2577 cursor.close(); 2578 2579 EventInfo[] events = { new EventInfo("normal0", 2580 "2008-05-01T00:00:00", 2581 "2008-05-02T00:00:00", 2582 false, 2583 DEFAULT_TIMEZONE) }; 2584 2585 Uri uri = insertEvent(calId, events[0]); 2586 assertNotNull(uri); 2587 2588 // check the inserted event 2589 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 2590 2591 // inject a new time zone 2592 getProvider().doProcessEventRawTimes(TIME_ZONE_AMERICA_ANCHORAGE, 2593 MOCK_TIME_ZONE_DATABASE_VERSION); 2594 2595 // check timezone database version 2596 assertEquals(MOCK_TIME_ZONE_DATABASE_VERSION, getProvider().getTimezoneDatabaseVersion()); 2597 2598 // check that the inserted event has *not* been updated 2599 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 2600 } 2601 2602 public static final Uri PROPERTIES_CONTENT_URI = 2603 Uri.parse("content://" + CalendarContract.AUTHORITY + "/properties"); 2604 2605 public static final int COLUMN_KEY_INDEX = 1; 2606 public static final int COLUMN_VALUE_INDEX = 0; 2607 2608 public void testGetProviderProperties() throws CalendarCache.CacheException { 2609 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2610 cleanCalendarDataTable(helper); 2611 CalendarCache cache = new CalendarCache(helper); 2612 2613 cache.writeTimezoneDatabaseVersion("2010k"); 2614 cache.writeTimezoneInstances("America/Denver"); 2615 cache.writeTimezoneInstancesPrevious("America/Los_Angeles"); 2616 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2617 2618 Cursor cursor = mResolver.query(PROPERTIES_CONTENT_URI, null, null, null, null); 2619 assertEquals(4, cursor.getCount()); 2620 2621 assertEquals(CalendarCache.COLUMN_NAME_KEY, cursor.getColumnName(COLUMN_KEY_INDEX)); 2622 assertEquals(CalendarCache.COLUMN_NAME_VALUE, cursor.getColumnName(COLUMN_VALUE_INDEX)); 2623 2624 Map<String, String> map = new HashMap<String, String>(); 2625 2626 while (cursor.moveToNext()) { 2627 String key = cursor.getString(COLUMN_KEY_INDEX); 2628 String value = cursor.getString(COLUMN_VALUE_INDEX); 2629 map.put(key, value); 2630 } 2631 2632 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_DATABASE_VERSION)); 2633 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_TYPE)); 2634 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_INSTANCES)); 2635 assertTrue(map.containsKey(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)); 2636 2637 assertEquals("2010k", map.get(CalendarCache.KEY_TIMEZONE_DATABASE_VERSION)); 2638 assertEquals("America/Denver", map.get(CalendarCache.KEY_TIMEZONE_INSTANCES)); 2639 assertEquals("America/Los_Angeles", map.get(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)); 2640 assertEquals(CalendarCache.TIMEZONE_TYPE_AUTO, map.get(CalendarCache.KEY_TIMEZONE_TYPE)); 2641 2642 cursor.close(); 2643 } 2644 2645 public void testGetProviderPropertiesByKey() throws CalendarCache.CacheException { 2646 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2647 cleanCalendarDataTable(helper); 2648 CalendarCache cache = new CalendarCache(helper); 2649 2650 cache.writeTimezoneDatabaseVersion("2010k"); 2651 cache.writeTimezoneInstances("America/Denver"); 2652 cache.writeTimezoneInstancesPrevious("America/Los_Angeles"); 2653 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2654 2655 checkValueForKey(CalendarCache.TIMEZONE_TYPE_AUTO, CalendarCache.KEY_TIMEZONE_TYPE); 2656 checkValueForKey("2010k", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2657 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES); 2658 checkValueForKey("America/Los_Angeles", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2659 } 2660 2661 private void checkValueForKey(String value, String key) { 2662 Cursor cursor = mResolver.query(PROPERTIES_CONTENT_URI, null, 2663 "key=?", new String[] {key}, null); 2664 2665 assertEquals(1, cursor.getCount()); 2666 assertTrue(cursor.moveToFirst()); 2667 assertEquals(cursor.getString(COLUMN_KEY_INDEX), key); 2668 assertEquals(cursor.getString(COLUMN_VALUE_INDEX), value); 2669 2670 cursor.close(); 2671 } 2672 2673 public void testUpdateProviderProperties() throws CalendarCache.CacheException { 2674 CalendarDatabaseHelper helper = (CalendarDatabaseHelper) getProvider().getDatabaseHelper(); 2675 cleanCalendarDataTable(helper); 2676 CalendarCache cache = new CalendarCache(helper); 2677 2678 String localTimezone = TimeZone.getDefault().getID(); 2679 2680 // Set initial value 2681 cache.writeTimezoneDatabaseVersion("2010k"); 2682 2683 updateValueForKey("2009s", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2684 checkValueForKey("2009s", CalendarCache.KEY_TIMEZONE_DATABASE_VERSION); 2685 2686 // Set initial values 2687 cache.writeTimezoneType(CalendarCache.TIMEZONE_TYPE_AUTO); 2688 cache.writeTimezoneInstances("America/Chicago"); 2689 cache.writeTimezoneInstancesPrevious("America/Denver"); 2690 2691 updateValueForKey(CalendarCache.TIMEZONE_TYPE_AUTO, CalendarCache.KEY_TIMEZONE_TYPE); 2692 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2693 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2694 2695 updateValueForKey(CalendarCache.TIMEZONE_TYPE_HOME, CalendarCache.KEY_TIMEZONE_TYPE); 2696 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES); 2697 checkValueForKey("America/Denver", CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2698 2699 // Set initial value 2700 cache.writeTimezoneInstancesPrevious(""); 2701 updateValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2702 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES); 2703 checkValueForKey(localTimezone, CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS); 2704 } 2705 2706 private void updateValueForKey(String value, String key) { 2707 ContentValues contentValues = new ContentValues(); 2708 contentValues.put(CalendarCache.COLUMN_NAME_VALUE, value); 2709 2710 int result = mResolver.update(PROPERTIES_CONTENT_URI, 2711 contentValues, 2712 CalendarCache.COLUMN_NAME_KEY + "=?", 2713 new String[] {key}); 2714 2715 assertEquals(1, result); 2716 } 2717 2718 public void testInsertOriginalTimezoneInExtProperties() throws Exception { 2719 int calId = insertCal("Calendar0", DEFAULT_TIMEZONE); 2720 2721 2722 EventInfo[] events = { new EventInfo("normal0", 2723 "2008-05-01T00:00:00", 2724 "2008-05-02T00:00:00", 2725 false, 2726 DEFAULT_TIMEZONE) }; 2727 2728 Uri eventUri = insertEvent(calId, events[0]); 2729 assertNotNull(eventUri); 2730 2731 long eventId = ContentUris.parseId(eventUri); 2732 assertTrue(eventId > -1); 2733 2734 // check the inserted event 2735 checkEvent(1, events[0].mTitle, events[0].mDtstart, events[0].mDtend, events[0].mAllDay); 2736 2737 // Should have 1 calendars and 1 event 2738 testQueryCount(CalendarContract.Calendars.CONTENT_URI, null /* where */, 1); 2739 testQueryCount(CalendarContract.Events.CONTENT_URI, null /* where */, 1); 2740 2741 // Verify that the original timezone is correct 2742 Cursor cursor = mResolver.query(CalendarContract.ExtendedProperties.CONTENT_URI, 2743 null/* projection */, 2744 "event_id=" + eventId, 2745 null /* selectionArgs */, 2746 null /* sortOrder */); 2747 try { 2748 // Should have 1 extended property for the original timezone 2749 assertEquals(1, cursor.getCount()); 2750 2751 if (cursor.moveToFirst()) { 2752 long id = cursor.getLong(1); 2753 assertEquals(id, eventId); 2754 2755 assertEquals(CalendarProvider2.EXT_PROP_ORIGINAL_TIMEZONE, cursor.getString(2)); 2756 assertEquals(DEFAULT_TIMEZONE, cursor.getString(3)); 2757 } 2758 } finally { 2759 cursor.close(); 2760 } 2761 } 2762 2763 /** 2764 * Verifies that the number of defined calendars meets expectations. 2765 * 2766 * @param expectedCount The number of calendars we expect to find. 2767 */ 2768 private void checkCalendarCount(int expectedCount) { 2769 Cursor cursor = mResolver.query(mCalendarsUri, 2770 null /* projection */, 2771 null /* selection */, 2772 null /* selectionArgs */, 2773 null /* sortOrder */); 2774 assertEquals(expectedCount, cursor.getCount()); 2775 cursor.close(); 2776 } 2777 2778 private void checkCalendarExists(int calId) { 2779 assertTrue(isCalendarExists(calId)); 2780 } 2781 2782 private void checkCalendarDoesNotExists(int calId) { 2783 assertFalse(isCalendarExists(calId)); 2784 } 2785 2786 private boolean isCalendarExists(int calId) { 2787 Cursor cursor = mResolver.query(mCalendarsUri, 2788 new String[] {Calendars._ID}, 2789 null /* selection */, 2790 null /* selectionArgs */, 2791 null /* sortOrder */); 2792 boolean found = false; 2793 while (cursor.moveToNext()) { 2794 if (calId == cursor.getInt(0)) { 2795 found = true; 2796 break; 2797 } 2798 } 2799 cursor.close(); 2800 return found; 2801 } 2802 2803 public void testDeleteAllCalendars() { 2804 checkCalendarCount(0); 2805 2806 insertCal("Calendar1", "America/Los_Angeles"); 2807 insertCal("Calendar2", "America/Los_Angeles"); 2808 2809 checkCalendarCount(2); 2810 2811 deleteMatchingCalendars(null /* selection */, null /* selectionArgs*/); 2812 checkCalendarCount(0); 2813 } 2814 2815 public void testDeleteCalendarsWithSelection() { 2816 checkCalendarCount(0); 2817 2818 int calId1 = insertCal("Calendar1", "America/Los_Angeles"); 2819 int calId2 = insertCal("Calendar2", "America/Los_Angeles"); 2820 2821 checkCalendarCount(2); 2822 checkCalendarExists(calId1); 2823 checkCalendarExists(calId2); 2824 2825 deleteMatchingCalendars(Calendars._ID + "=" + calId2, null /* selectionArgs*/); 2826 checkCalendarCount(1); 2827 checkCalendarExists(calId1); 2828 checkCalendarDoesNotExists(calId2); 2829 } 2830 2831 public void testDeleteCalendarsWithSelectionAndArgs() { 2832 checkCalendarCount(0); 2833 2834 int calId1 = insertCal("Calendar1", "America/Los_Angeles"); 2835 int calId2 = insertCal("Calendar2", "America/Los_Angeles"); 2836 2837 checkCalendarCount(2); 2838 checkCalendarExists(calId1); 2839 checkCalendarExists(calId2); 2840 2841 deleteMatchingCalendars(Calendars._ID + "=?", 2842 new String[] { Integer.toString(calId2) }); 2843 checkCalendarCount(1); 2844 checkCalendarExists(calId1); 2845 checkCalendarDoesNotExists(calId2); 2846 2847 deleteMatchingCalendars(Calendars._ID + "=?" + " AND " + Calendars.NAME + "=?", 2848 new String[] { Integer.toString(calId1), "Calendar1" }); 2849 checkCalendarCount(0); 2850 } 2851} 2852