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