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