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