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