1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.calendar.event; 18 19import android.content.ContentProvider; 20import android.content.ContentProviderOperation; 21import android.content.ContentProviderResult; 22import android.content.ContentValues; 23import android.content.res.Resources; 24import android.database.Cursor; 25import android.database.MatrixCursor; 26import android.net.Uri; 27import android.provider.CalendarContract.Attendees; 28import android.provider.CalendarContract.Events; 29import android.provider.CalendarContract.Reminders; 30import android.test.AndroidTestCase; 31import android.test.mock.MockResources; 32import android.test.suitebuilder.annotation.SmallTest; 33import android.test.suitebuilder.annotation.Smoke; 34import android.text.format.DateUtils; 35import android.text.format.Time; 36import android.text.util.Rfc822Token; 37 38import com.android.calendar.AbstractCalendarActivity; 39import com.android.calendar.AsyncQueryService; 40import com.android.calendar.CalendarEventModel; 41import com.android.calendar.CalendarEventModel.ReminderEntry; 42import com.android.calendar.R; 43import com.android.calendar.Utils; 44import com.android.common.Rfc822Validator; 45 46import java.util.ArrayList; 47import java.util.Arrays; 48import java.util.Calendar; 49import java.util.LinkedHashSet; 50import java.util.TimeZone; 51 52public class EditEventHelperTest extends AndroidTestCase { 53 private static final int TEST_EVENT_ID = 1; 54 private static final int TEST_EVENT_INDEX_ID = 0; 55 private static final long TEST_END = 1272931200000L; 56 private static long TEST_END2 = 1272956400000L; 57 private static final long TEST_START = 1272844800000L; 58 private static long TEST_START2 = 1272870000000L; 59 private static final String LOCAL_TZ = TimeZone.getDefault().getID(); 60 61 private static final int SAVE_EVENT_NEW_EVENT = 1; 62 private static final int SAVE_EVENT_MOD_RECUR = 2; 63 private static final int SAVE_EVENT_RECUR_TO_NORECUR = 3; 64 private static final int SAVE_EVENT_NORECUR_TO_RECUR= 4; 65 private static final int SAVE_EVENT_MOD_NORECUR = 5; 66 private static final int SAVE_EVENT_MOD_INSTANCE = 6; 67 private static final int SAVE_EVENT_ALLFOLLOW_TO_NORECUR = 7; 68 private static final int SAVE_EVENT_FIRST_TO_NORECUR = 8; 69 private static final int SAVE_EVENT_FIRST_TO_RECUR = 9; 70 private static final int SAVE_EVENT_ALLFOLLOW_TO_RECUR = 10; 71 72 /* These should match up with EditEventHelper.EVENT_PROJECTION. 73 * Note that spaces and commas have been removed to allow for easier sanitation. 74 */ 75 private static String[] TEST_CURSOR_DATA = new String[] { 76 Integer.toString(TEST_EVENT_ID), // 0 _id 77 "The_Question", // 1 title 78 "Evaluating_Life_the_Universe_and_Everything", // 2 description 79 "Earth_Mk2", // 3 location 80 "1", // 4 All Day 81 "0", // 5 Has alarm 82 "2", // 6 Calendar id 83 "1272844800000", // 7 dtstart, Monday, May 3rd midnight UTC 84 "1272931200000", // 8 dtend, Tuesday, May 4th midnight UTC 85 "P3652421990D", // 9 duration, (10 million years) 86 "UTC", // 10 event timezone 87 "FREQ=DAILY;WKST=SU", // 11 rrule 88 "unique_per_calendar_stuff", // 12 sync id 89 "0", // 13 transparency/availability 90 "3", // 14 visibility/access level 91 "steve@gmail.com", // 15 owner account 92 "1", // 16 has attendee data 93 null, //17 originalSyncId 94 "organizer@gmail.com", // 18 organizer 95 "0", // 19 guest can modify 96 "-1", // 20 original id 97 "1", // 21 event status 98 "-339611", // 22 calendar color 99 "-2350809", // 23 event color 100 "11" // 24 event color key 101 }; 102 103 private static final String AUTHORITY_URI = "content://EditEventHelperAuthority/"; 104 private static final String AUTHORITY = "EditEventHelperAuthority"; 105 106 private static final String TEST_ADDRESSES = 107 "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " + 108 "one.two.three@email.grue"; 109 private static final String TEST_ADDRESSES2 = 110 "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " + 111 "different@email.bit"; 112 private static final String TEST_ADDRESSES3 = 113 "ad1@email.com, \"First Last\" <first@email.com> (comment), " + 114 "different@email.bit"; 115 private static final String TEST_ADDRESSES4 = 116 "ad1@email.com, \"First Last\" <first@email.com> (comment), " + 117 "one.two.three@email.grue"; 118 119 120 private static final String TAG = "EEHTest"; 121 122 private Rfc822Validator mEmailValidator; 123 private CalendarEventModel mModel1; 124 private CalendarEventModel mModel2; 125 126 private ContentValues mValues; 127 private ContentValues mExpectedValues; 128 129 private EditEventHelper mHelper; 130 private AbstractCalendarActivity mActivity; 131 private int mCurrentSaveTest = 0; 132 133 @Override 134 public void setUp() { 135 Time time = new Time(Time.TIMEZONE_UTC); 136 time.set(TEST_START); 137 time.timezone = LOCAL_TZ; 138 TEST_START2 = time.normalize(true); 139 140 time.timezone = Time.TIMEZONE_UTC; 141 time.set(TEST_END); 142 time.timezone = LOCAL_TZ; 143 TEST_END2 = time.normalize(true); 144 145 mEmailValidator = new Rfc822Validator(null); 146 } 147 148 private class MockAbsCalendarActivity extends AbstractCalendarActivity { 149 @Override 150 public AsyncQueryService getAsyncQueryService() { 151 if (mService == null) { 152 mService = new AsyncQueryService(this) { 153 @Override 154 public void startBatch(int token, Object cookie, String authority, 155 ArrayList<ContentProviderOperation> cpo, long delayMillis) { 156 mockApplyBatch(authority, cpo); 157 } 158 }; 159 } 160 return mService; 161 } 162 163 @Override 164 public Resources getResources() { 165 Resources res = new MockResources() { 166 @Override 167 // The actual selects singular vs plural as well and in the given language 168 public String getQuantityString(int id, int quantity) { 169 if (id == R.plurals.Nmins) { 170 return quantity + " mins"; 171 } 172 if (id == R.plurals.Nminutes) { 173 return quantity + " minutes"; 174 } 175 if (id == R.plurals.Nhours) { 176 return quantity + " hours"; 177 } 178 if (id == R.plurals.Ndays) { 179 return quantity + " days"; 180 } 181 return id + " " + quantity; 182 } 183 }; 184 return res; 185 } 186 } 187 188 private AbstractCalendarActivity buildTestContext() { 189 MockAbsCalendarActivity context = new MockAbsCalendarActivity(); 190 return context; 191 } 192 193 private ContentProviderResult[] mockApplyBatch(String authority, 194 ArrayList<ContentProviderOperation> operations) { 195 switch (mCurrentSaveTest) { 196 case SAVE_EVENT_NEW_EVENT: 197 // new recurring event 198 verifySaveEventNewEvent(operations); 199 break; 200 case SAVE_EVENT_MOD_RECUR: 201 // update to recurring event 202 verifySaveEventModifyRecurring(operations); 203 break; 204 case SAVE_EVENT_RECUR_TO_NORECUR: 205 // replace recurring event with non-recurring event 206 verifySaveEventRecurringToNonRecurring(operations); 207 break; 208 case SAVE_EVENT_NORECUR_TO_RECUR: 209 // update non-recurring event with recurring event 210 verifySaveEventNonRecurringToRecurring(operations); 211 break; 212 case SAVE_EVENT_MOD_NORECUR: 213 // update to non-recurring 214 verifySaveEventUpdateNonRecurring(operations); 215 break; 216 case SAVE_EVENT_MOD_INSTANCE: 217 // update to single instance of recurring event 218 verifySaveEventModifySingleInstance(operations); 219 break; 220 case SAVE_EVENT_ALLFOLLOW_TO_NORECUR: 221 // update all following with non-recurring event 222 verifySaveEventModifyAllFollowingWithNonRecurring(operations); 223 break; 224 case SAVE_EVENT_FIRST_TO_NORECUR: 225 // update all following with non-recurring event on first event in series 226 verifySaveEventModifyAllFollowingFirstWithNonRecurring(operations); 227 break; 228 case SAVE_EVENT_FIRST_TO_RECUR: 229 // update all following with recurring event on first event in series 230 verifySaveEventModifyAllFollowingFirstWithRecurring(operations); 231 break; 232 case SAVE_EVENT_ALLFOLLOW_TO_RECUR: 233 // update all following with recurring event on second event in series 234 verifySaveEventModifyAllFollowingWithRecurring(operations); 235 break; 236 } 237 return new ContentProviderResult[] {new ContentProviderResult(5)}; 238 } 239 240 private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps, int id) { 241 addOwnerAttendee(); 242 ContentProviderOperation.Builder b; 243 b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues); 244 b.withValueBackReference(Reminders.EVENT_ID, id); 245 expectedOps.add(b.build()); 246 } 247 248 private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps) { 249 addOwnerAttendee(); 250 mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID); 251 ContentProviderOperation.Builder b; 252 b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues); 253 expectedOps.add(b.build()); 254 } 255 256 257 // Some tests set the time values to one day later, this does that move in the values 258 private void moveExpectedTimeValuesForwardOneDay() { 259 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 260 mExpectedValues.put(Events.DTSTART, TEST_START + dayInMs); 261 mExpectedValues.put(Events.DTEND, TEST_END + dayInMs); 262 } 263 264 // Duplicates the delete and add for changing a single email address 265 private void addAttendeeChangesOps(ArrayList<ContentProviderOperation> expectedOps) { 266 ContentProviderOperation.Builder b = 267 ContentProviderOperation.newDelete(Attendees.CONTENT_URI); 268 b.withSelection(EditEventHelper.ATTENDEES_DELETE_PREFIX + "?)", 269 new String[] {"one.two.three@email.grue"}); 270 expectedOps.add(b.build()); 271 272 mExpectedValues.clear(); 273 mExpectedValues.put(Attendees.ATTENDEE_NAME, "different@email.bit"); 274 mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit"); 275 mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); 276 mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 277 mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); 278 mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID); 279 b = ContentProviderOperation 280 .newInsert(Attendees.CONTENT_URI) 281 .withValues(mExpectedValues); 282 expectedOps.add(b.build()); 283 } 284 285 // This is a commonly added set of values 286 private void addOwnerAttendee() { 287 mExpectedValues.clear(); 288 mExpectedValues.put(Attendees.ATTENDEE_EMAIL, mModel1.mOwnerAccount); 289 mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); 290 mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 291 mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); 292 } 293 294 /** Some tests add all the attendees to the db, the names and emails should match 295 * with {@link #TEST_ADDRESSES2} minus the 'no good' 296 */ 297 private void addTestAttendees(ArrayList<ContentProviderOperation> ops, 298 boolean newEvent, int id) { 299 ContentProviderOperation.Builder b; 300 mExpectedValues.clear(); 301 mExpectedValues.put(Attendees.ATTENDEE_NAME, "ad1@email.com"); 302 mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "ad1@email.com"); 303 mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); 304 mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 305 mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); 306 307 if (newEvent) { 308 b = ContentProviderOperation 309 .newInsert(Attendees.CONTENT_URI) 310 .withValues(mExpectedValues); 311 b.withValueBackReference(Attendees.EVENT_ID, id); 312 } else { 313 mExpectedValues.put(Attendees.EVENT_ID, id); 314 b = ContentProviderOperation 315 .newInsert(Attendees.CONTENT_URI) 316 .withValues(mExpectedValues); 317 } 318 ops.add(b.build()); 319 320 mExpectedValues.clear(); 321 mExpectedValues.put(Attendees.ATTENDEE_NAME, "First Last"); 322 mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "first@email.com"); 323 mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); 324 mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 325 mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); 326 327 if (newEvent) { 328 b = ContentProviderOperation 329 .newInsert(Attendees.CONTENT_URI) 330 .withValues(mExpectedValues); 331 b.withValueBackReference(Attendees.EVENT_ID, id); 332 } else { 333 mExpectedValues.put(Attendees.EVENT_ID, id); 334 b = ContentProviderOperation 335 .newInsert(Attendees.CONTENT_URI) 336 .withValues(mExpectedValues); 337 } 338 ops.add(b.build()); 339 340 mExpectedValues.clear(); 341 mExpectedValues.put(Attendees.ATTENDEE_NAME, "different@email.bit"); 342 mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit"); 343 mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); 344 mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED); 345 mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); 346 347 if (newEvent) { 348 b = ContentProviderOperation 349 .newInsert(Attendees.CONTENT_URI) 350 .withValues(mExpectedValues); 351 b.withValueBackReference(Attendees.EVENT_ID, id); 352 } else { 353 mExpectedValues.put(Attendees.EVENT_ID, id); 354 b = ContentProviderOperation 355 .newInsert(Attendees.CONTENT_URI) 356 .withValues(mExpectedValues); 357 } 358 ops.add(b.build()); 359 } 360 361 @Smoke 362 @SmallTest 363 public void testSaveEventFailures() { 364 mActivity = buildTestContext(); 365 mHelper = new EditEventHelper(mActivity, null); 366 367 mModel1 = buildTestModel(); 368 mModel2 = buildTestModel(); 369 370 // saveEvent should return false early if: 371 // -it was set to not ok 372 // -the model was null 373 // -the event doesn't represent the same event as the original event 374 // -there's a uri but an original event is not provided 375 mHelper.mEventOk = false; 376 assertFalse(mHelper.saveEvent(null, null, 0)); 377 mHelper.mEventOk = true; 378 assertFalse(mHelper.saveEvent(null, null, 0)); 379 mModel2.mId = 13; 380 assertFalse(mHelper.saveEvent(mModel1, mModel2, 0)); 381 mModel2.mId = mModel1.mId; 382 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 383 mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 384 assertFalse(mHelper.saveEvent(mModel1, null, 0)); 385 } 386 387 @Smoke 388 @SmallTest 389 public void testSaveEventNewEvent() { 390 // Creates a model of a new event for saving 391 mActivity = buildTestContext(); 392 mHelper = new EditEventHelper(mActivity, null); 393 394 mModel1 = buildTestModel(); 395 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 396 mCurrentSaveTest = SAVE_EVENT_NEW_EVENT; 397 398 assertTrue(mHelper.saveEvent(mModel1, null, 0)); 399 } 400 401 private boolean verifySaveEventNewEvent(ArrayList<ContentProviderOperation> ops) { 402 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 403 int br_id = 0; 404 mExpectedValues = buildTestValues(); 405 mExpectedValues.put(Events.HAS_ALARM, 0); 406 mExpectedValues.put(Events.HAS_ATTENDEE_DATA, 1); 407 ContentProviderOperation.Builder b = ContentProviderOperation 408 .newInsert(Events.CONTENT_URI) 409 .withValues(mExpectedValues); 410 expectedOps.add(b.build()); 411 412 // This call has a separate unit test so we'll use it to simplify making the expected vals 413 mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders, 414 new ArrayList<ReminderEntry>(), true); 415 416 addOwnerAttendeeToOps(expectedOps, br_id); 417 418 addTestAttendees(expectedOps, true, br_id); 419 420 assertEquals(expectedOps, ops); 421 return true; 422 } 423 424 @Smoke 425 @SmallTest 426 public void testSaveEventModifyRecurring() { 427 // Creates an original and an updated recurring event model 428 mActivity = buildTestContext(); 429 mHelper = new EditEventHelper(mActivity, null); 430 431 mModel1 = buildTestModel(); 432 mModel2 = buildTestModel(); 433 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 434 435 // Updating a recurring event with a new attendee list 436 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 437 // And a new start time to ensure the time fields aren't removed 438 mModel1.mOriginalStart = TEST_START; 439 440 // The original model is assumed correct so drop the no good bit 441 mModel2.addAttendees(TEST_ADDRESSES4, null); 442 mCurrentSaveTest = SAVE_EVENT_MOD_RECUR; 443 444 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); 445 } 446 447 private boolean verifySaveEventModifyRecurring(ArrayList<ContentProviderOperation> ops) { 448 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 449 int br_id = 0; 450 mExpectedValues = buildTestValues(); 451 mExpectedValues.put(Events.HAS_ALARM, 0); 452 // This is tested elsewhere, used for convenience here 453 mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues, 454 EditEventHelper.MODIFY_ALL); 455 456 expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( 457 mExpectedValues).build()); 458 459 // This call has a separate unit test so we'll use it to simplify making the expected vals 460 mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, 461 mModel2.mReminders, false); 462 463 addOwnerAttendeeToOps(expectedOps); 464 addAttendeeChangesOps(expectedOps); 465 466 assertEquals(expectedOps, ops); 467 return true; 468 } 469 470 @Smoke 471 @SmallTest 472 public void testSaveEventRecurringToNonRecurring() { 473 // Creates an original and an updated recurring event model 474 mActivity = buildTestContext(); 475 mHelper = new EditEventHelper(mActivity, null); 476 477 mModel1 = buildTestModel(); 478 mModel2 = buildTestModel(); 479 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 480 481 // Updating a recurring event with a new attendee list 482 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 483 // And a new start time to ensure the time fields aren't removed 484 mModel1.mOriginalStart = TEST_START; 485 486 // The original model is assumed correct so drop the no good bit 487 mModel2.addAttendees(TEST_ADDRESSES4, null); 488 489 // Replace an existing recurring event with a non-recurring event 490 mModel1.mRrule = null; 491 mModel1.mEnd = TEST_END; 492 mCurrentSaveTest = SAVE_EVENT_RECUR_TO_NORECUR; 493 494 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); 495 } 496 497 private boolean verifySaveEventRecurringToNonRecurring(ArrayList<ContentProviderOperation> ops) 498 { 499 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 500 int id = 0; 501 mExpectedValues = buildNonRecurringTestValues(); 502 mExpectedValues.put(Events.HAS_ALARM, 0); 503 // This is tested elsewhere, used for convenience here 504 mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, 505 EditEventHelper.MODIFY_ALL); 506 507 expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build()); 508 id = expectedOps.size(); 509 expectedOps.add(ContentProviderOperation 510 .newInsert(Events.CONTENT_URI) 511 .withValues(mExpectedValues) 512 .build()); 513 514 mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, 515 mModel2.mReminders, true); 516 517 addOwnerAttendeeToOps(expectedOps, id); 518 519 addTestAttendees(expectedOps, true, id); 520 521 assertEquals(expectedOps, ops); 522 return true; 523 } 524 525 @Smoke 526 @SmallTest 527 public void testSaveEventNonRecurringToRecurring() { 528 // Creates an original non-recurring and an updated recurring event model 529 mActivity = buildTestContext(); 530 mHelper = new EditEventHelper(mActivity, null); 531 532 mModel1 = buildTestModel(); 533 mModel2 = buildTestModel(); 534 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 535 536 // Updating a recurring event with a new attendee list 537 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 538 // And a new start time to ensure the time fields aren't removed 539 mModel1.mOriginalStart = TEST_START; 540 541 // The original model is assumed correct so drop the no good bit 542 mModel2.addAttendees(TEST_ADDRESSES4, null); 543 544 mModel2.mRrule = null; 545 mModel2.mEnd = TEST_END; 546 mCurrentSaveTest = SAVE_EVENT_NORECUR_TO_RECUR; 547 548 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); 549 } 550 551 private boolean verifySaveEventNonRecurringToRecurring(ArrayList<ContentProviderOperation> ops) 552 { 553 // Changing a non-recurring event to a recurring event should generate the same operations 554 // as just modifying a recurring event. 555 return verifySaveEventModifyRecurring(ops); 556 } 557 558 @Smoke 559 @SmallTest 560 public void testSaveEventUpdateNonRecurring() { 561 // Creates an original non-recurring and an updated recurring event model 562 mActivity = buildTestContext(); 563 mHelper = new EditEventHelper(mActivity, null); 564 565 mModel1 = buildTestModel(); 566 mModel2 = buildTestModel(); 567 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 568 569 // Updating a recurring event with a new attendee list 570 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 571 // And a new start time to ensure the time fields aren't removed 572 mModel1.mOriginalStart = TEST_START; 573 574 // The original model is assumed correct so drop the no good bit 575 mModel2.addAttendees(TEST_ADDRESSES4, null); 576 577 mModel2.mRrule = null; 578 mModel2.mEnd = TEST_END2; 579 mModel1.mRrule = null; 580 mModel1.mEnd = TEST_END2; 581 mCurrentSaveTest = SAVE_EVENT_MOD_NORECUR; 582 583 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL)); 584 } 585 586 private boolean verifySaveEventUpdateNonRecurring(ArrayList<ContentProviderOperation> ops) { 587 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 588 int id = TEST_EVENT_ID; 589 mExpectedValues = buildNonRecurringTestValues(); 590 mExpectedValues.put(Events.HAS_ALARM, 0); 591 // This is tested elsewhere, used for convenience here 592 mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, 593 EditEventHelper.MODIFY_ALL); 594 expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( 595 mExpectedValues).build()); 596 // This call has a separate unit test so we'll use it to simplify making the expected vals 597 mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, 598 mModel2.mReminders, false); 599 addOwnerAttendeeToOps(expectedOps); 600 addAttendeeChangesOps(expectedOps); 601 602 assertEquals(expectedOps, ops); 603 return true; 604 } 605 606 @Smoke 607 @SmallTest 608 public void testSaveEventModifySingleInstance() { 609 // Creates an original non-recurring and an updated recurring event model 610 mActivity = buildTestContext(); 611 mHelper = new EditEventHelper(mActivity, null); 612 613 mModel1 = buildTestModel(); 614 mModel2 = buildTestModel(); 615 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 616 617 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 618 // And a new start time to ensure the time fields aren't removed 619 mModel1.mOriginalStart = TEST_START; 620 621 // The original model is assumed correct so drop the no good bit 622 mModel2.addAttendees(TEST_ADDRESSES4, null); 623 624 // Modify the second instance of the event 625 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 626 mModel1.mRrule = null; 627 mModel1.mEnd = TEST_END + dayInMs; 628 mModel1.mStart += dayInMs; 629 mModel1.mOriginalStart = mModel1.mStart; 630 631 mCurrentSaveTest = SAVE_EVENT_MOD_INSTANCE; 632 // Only modify this instance 633 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_SELECTED)); 634 } 635 636 private boolean verifySaveEventModifySingleInstance(ArrayList<ContentProviderOperation> ops) { 637 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 638 int id = 0; 639 mExpectedValues = buildNonRecurringTestValues(); 640 mExpectedValues.put(Events.HAS_ALARM, 0); 641 // This is tested elsewhere, used for convenience here 642 mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues, 643 EditEventHelper.MODIFY_ALL); 644 645 moveExpectedTimeValuesForwardOneDay(); 646 mExpectedValues.put(Events.ORIGINAL_SYNC_ID, mModel2.mSyncId); 647 mExpectedValues.put(Events.ORIGINAL_INSTANCE_TIME, mModel1.mOriginalStart); 648 mExpectedValues.put(Events.ORIGINAL_ALL_DAY, 1); 649 650 ContentProviderOperation.Builder b = ContentProviderOperation 651 .newInsert(Events.CONTENT_URI) 652 .withValues(mExpectedValues); 653 expectedOps.add(b.build()); 654 655 mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, 656 mModel2.mReminders, true); 657 658 addOwnerAttendeeToOps(expectedOps, id); 659 660 addTestAttendees(expectedOps, true, id); 661 662 assertEquals(expectedOps, ops); 663 return true; 664 } 665 666 @Smoke 667 @SmallTest 668 public void testSaveEventModifyAllFollowingWithNonRecurring() { 669 // Creates an original and an updated recurring event model. The update starts on the 2nd 670 // instance of the original. 671 mActivity = buildTestContext(); 672 mHelper = new EditEventHelper(mActivity, null); 673 674 mModel1 = buildTestModel(); 675 mModel2 = buildTestModel(); 676 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 677 678 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 679 mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 680 681 // The original model is assumed correct so drop the no good bit 682 mModel2.addAttendees(TEST_ADDRESSES4, null); 683 684 // Modify the second instance of the event 685 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 686 mModel1.mRrule = null; 687 mModel1.mEnd = TEST_END + dayInMs; 688 mModel1.mStart += dayInMs; 689 mModel1.mOriginalStart = mModel1.mStart; 690 691 mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_NORECUR; 692 // Only modify this instance 693 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); 694 } 695 696 private boolean verifySaveEventModifyAllFollowingWithNonRecurring( 697 ArrayList<ContentProviderOperation> ops) { 698 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 699 int id = 0; 700 mExpectedValues = buildNonRecurringTestValues(); 701 mExpectedValues.put(Events.HAS_ALARM, 0); 702 moveExpectedTimeValuesForwardOneDay(); 703 // This has a separate test 704 mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart); 705 id = expectedOps.size(); 706 expectedOps.add(ContentProviderOperation 707 .newInsert(Events.CONTENT_URI) 708 .withValues(mExpectedValues) 709 .build()); 710 711 mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, 712 mModel2.mReminders, true); 713 714 addOwnerAttendeeToOps(expectedOps, id); 715 716 addTestAttendees(expectedOps, true, id); 717 718 assertEquals(expectedOps, ops); 719 return true; 720 } 721 722 @Smoke 723 @SmallTest 724 public void testSaveEventModifyAllFollowingFirstWithNonRecurring() { 725 // Creates an original recurring and an updated non-recurring event model for the first 726 // instance. This should replace the original event with a non-recurring event. 727 mActivity = buildTestContext(); 728 mHelper = new EditEventHelper(mActivity, null); 729 730 mModel1 = buildTestModel(); 731 mModel2 = buildTestModel(); 732 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 733 734 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 735 mModel2.mUri = mModel1.mUri; 736 // And a new start time to ensure the time fields aren't removed 737 mModel1.mOriginalStart = TEST_START; 738 739 // The original model is assumed correct so drop the no good bit 740 mModel2.addAttendees(TEST_ADDRESSES3, null); 741 742 // Move the event one day but keep original start set to the first instance 743 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 744 mModel1.mRrule = null; 745 mModel1.mEnd = TEST_END + dayInMs; 746 mModel1.mStart += dayInMs; 747 748 mCurrentSaveTest = SAVE_EVENT_FIRST_TO_NORECUR; 749 // Only modify this instance 750 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); 751 } 752 753 private boolean verifySaveEventModifyAllFollowingFirstWithNonRecurring( 754 ArrayList<ContentProviderOperation> ops) { 755 756 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 757 int id = 0; 758 mExpectedValues = buildNonRecurringTestValues(); 759 mExpectedValues.put(Events.HAS_ALARM, 0); 760 moveExpectedTimeValuesForwardOneDay(); 761 762 expectedOps.add(ContentProviderOperation.newDelete(Uri.parse(mModel1.mUri)).build()); 763 id = expectedOps.size(); 764 expectedOps.add(ContentProviderOperation 765 .newInsert(Events.CONTENT_URI) 766 .withValues(mExpectedValues) 767 .build()); 768 769 mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminders, 770 mModel2.mReminders, true); 771 772 addOwnerAttendeeToOps(expectedOps, id); 773 774 addTestAttendees(expectedOps, true, id); 775 776 assertEquals(expectedOps, ops); 777 return true; 778 } 779 780 @Smoke 781 @SmallTest 782 public void testSaveEventModifyAllFollowingFirstWithRecurring() { 783 // Creates an original recurring and an updated recurring event model for the first instance 784 // This should replace the original event with a new recurrence 785 mActivity = buildTestContext(); 786 mHelper = new EditEventHelper(mActivity, null); 787 788 mModel1 = buildTestModel(); 789 mModel2 = buildTestModel(); 790 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 791 792 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 793 mModel2.mUri = mModel1.mUri; 794 // And a new start time to ensure the time fields aren't removed 795 mModel1.mOriginalStart = TEST_START; 796 797 // The original model is assumed correct so drop the no good bit 798 mModel2.addAttendees(TEST_ADDRESSES4, null); 799 800 // Move the event one day but keep original start set to the first instance 801 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 802 mModel1.mStart += dayInMs; 803 804 mCurrentSaveTest = SAVE_EVENT_FIRST_TO_RECUR; 805 // Only modify this instance 806 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); 807 } 808 809 private boolean verifySaveEventModifyAllFollowingFirstWithRecurring( 810 ArrayList<ContentProviderOperation> ops) { 811 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 812 int br_id = 0; 813 mExpectedValues = buildTestValues(); 814 mExpectedValues.put(Events.HAS_ALARM, 0); 815 moveExpectedTimeValuesForwardOneDay(); 816 mExpectedValues.put(Events.DTEND, (Long)null); 817 // This is tested elsewhere, used for convenience here 818 mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues, 819 EditEventHelper.MODIFY_ALL_FOLLOWING); 820 821 expectedOps.add(ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues( 822 mExpectedValues).build()); 823 824 // This call has a separate unit test so we'll use it to simplify making the expected vals 825 mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminders, 826 mModel2.mReminders, true); 827 828 addOwnerAttendeeToOps(expectedOps); 829 addAttendeeChangesOps(expectedOps); 830 831 assertEquals(expectedOps, ops); 832 return true; 833 } 834 835 @Smoke 836 @SmallTest 837 public void testSaveEventModifyAllFollowingWithRecurring() { 838 // Creates an original recurring and an updated recurring event model 839 // for the second instance. This should end the original recurrence and add a new 840 // recurrence. 841 mActivity = buildTestContext(); 842 mHelper = new EditEventHelper(mActivity, null); 843 844 mModel1 = buildTestModel(); 845 mModel2 = buildTestModel(); 846 mModel1.addAttendees(TEST_ADDRESSES2, mEmailValidator); 847 848 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 849 mModel2.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 850 851 // The original model is assumed correct so drop the no good bit 852 mModel2.addAttendees(TEST_ADDRESSES4, null); 853 854 // Move the event one day and the original start so it references the second instance 855 long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000; 856 mModel1.mStart += dayInMs; 857 mModel1.mOriginalStart = mModel1.mStart; 858 859 mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_RECUR; 860 // Only modify this instance 861 assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING)); 862 } 863 864 private boolean verifySaveEventModifyAllFollowingWithRecurring( 865 ArrayList<ContentProviderOperation> ops) { 866 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 867 int br_id = 0; 868 mExpectedValues = buildTestValues(); 869 mExpectedValues.put(Events.HAS_ALARM, 0); 870 moveExpectedTimeValuesForwardOneDay(); 871 mExpectedValues.put(Events.DTEND, (Long)null); 872 // This is tested elsewhere, used for convenience here 873 mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart); 874 875 br_id = expectedOps.size(); 876 expectedOps.add(ContentProviderOperation 877 .newInsert(Events.CONTENT_URI) 878 .withValues(mExpectedValues) 879 .build()); 880 881 // This call has a separate unit test so we'll use it to simplify making the expected vals 882 mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminders, 883 mModel2.mReminders, true); 884 885 addOwnerAttendeeToOps(expectedOps, br_id); 886 887 addTestAttendees(expectedOps, true, br_id); 888 889 assertEquals(expectedOps, ops); 890 return true; 891 } 892 893 @Smoke 894 @SmallTest 895 public void testGetAddressesFromList() { 896 mActivity = buildTestContext(); 897 mHelper = new EditEventHelper(mActivity, null); 898 899 LinkedHashSet<Rfc822Token> expected = new LinkedHashSet<Rfc822Token>(); 900 expected.add(new Rfc822Token(null, "ad1@email.com", "")); 901 expected.add(new Rfc822Token("First Last", "first@email.com", "comment")); 902 expected.add(new Rfc822Token(null, "one.two.three@email.grue", "")); 903 904 LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES, 905 new Rfc822Validator(null)); 906 assertEquals(expected, actual); 907 } 908 909 @Smoke 910 @SmallTest 911 public void testConstructDefaultStartTime() { 912 mActivity = buildTestContext(); 913 mHelper = new EditEventHelper(mActivity, null); 914 915 long now = 0; 916 long expected = now + 30 * DateUtils.MINUTE_IN_MILLIS; 917 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 918 919 // 2:00 -> 2:30 920 now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST) 921 expected = now + 30 * DateUtils.MINUTE_IN_MILLIS; 922 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 923 924 // 2:01 -> 2:30 925 now += DateUtils.MINUTE_IN_MILLIS; 926 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 927 928 // 2:02 -> 2:30 929 now += DateUtils.MINUTE_IN_MILLIS; 930 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 931 932 // 2:32 -> 3:00 933 now += 30 * DateUtils.MINUTE_IN_MILLIS; 934 expected += 30 * DateUtils.MINUTE_IN_MILLIS; 935 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 936 937 // 2:33 -> 3:00 938 now += DateUtils.MINUTE_IN_MILLIS; 939 assertEquals(expected, mHelper.constructDefaultStartTime(now)); 940 941 } 942 943 @Smoke 944 @SmallTest 945 public void testConstructDefaultEndTime() { 946 mActivity = buildTestContext(); 947 mHelper = new EditEventHelper(mActivity, null); 948 949 long start = 1262340000000L; 950 long expected = start + DateUtils.HOUR_IN_MILLIS; 951 assertEquals(expected, mHelper.constructDefaultEndTime(start)); 952 } 953 954 @Smoke 955 @SmallTest 956 public void testCheckTimeDependentFieldsNoChanges() { 957 mActivity = buildTestContext(); 958 mHelper = new EditEventHelper(mActivity, null); 959 960 mModel1 = buildTestModel(); 961 mModel2 = buildTestModel(); 962 mModel2.mRrule = null; 963 964 mValues = buildTestValues(); 965 mExpectedValues = buildTestValues(); 966 967 // if any time/recurrence vals are different but there's no new rrule it 968 // shouldn't change 969 mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, EditEventHelper.MODIFY_ALL); 970 assertEquals(mExpectedValues, mValues); 971 972 // also, if vals are different and it's not modifying all it shouldn't 973 // change. 974 mModel2.mRrule = "something else"; 975 mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, 976 EditEventHelper.MODIFY_SELECTED); 977 assertEquals(mExpectedValues, mValues); 978 979 // if vals changed and modify all is selected dtstart should be updated 980 // by the difference 981 // between originalStart and start 982 mModel2.mOriginalStart = mModel2.mStart + 60000; // set the old time to 983 // one minute later 984 mModel2.mStart += 120000; // move the event another 1 minute. 985 986 // shouldn't change for an allday event 987 // expectedVals.put(Events.DTSTART, mModel1.mStart + 60000); // should 988 // now be 1 minute later 989 // dtstart2 shouldn't change since it gets rezeroed in the local 990 // timezone for allDay events 991 992 mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, 993 EditEventHelper.MODIFY_SELECTED); 994 assertEquals(mExpectedValues, mValues); 995 } 996 997 @Smoke 998 @SmallTest 999 public void testCheckTimeDependentFieldsChanges() { 1000 mActivity = buildTestContext(); 1001 mHelper = new EditEventHelper(mActivity, null); 1002 1003 mModel1 = buildTestModel(); 1004 mModel2 = buildTestModel(); 1005 mModel2.mRrule = null; 1006 1007 mValues = buildTestValues(); 1008 mExpectedValues = buildTestValues(); 1009 1010 // if all the time values are the same it should remove them from vals 1011 mModel2.mRrule = mModel1.mRrule; 1012 mModel2.mStart = mModel1.mStart; 1013 mModel2.mOriginalStart = mModel2.mStart; 1014 1015 mExpectedValues.remove(Events.DTSTART); 1016 mExpectedValues.remove(Events.DTEND); 1017 mExpectedValues.remove(Events.DURATION); 1018 mExpectedValues.remove(Events.ALL_DAY); 1019 mExpectedValues.remove(Events.RRULE); 1020 mExpectedValues.remove(Events.EVENT_TIMEZONE); 1021 1022 mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, 1023 EditEventHelper.MODIFY_SELECTED); 1024 assertEquals(mExpectedValues, mValues); 1025 1026 } 1027 1028 @Smoke 1029 @SmallTest 1030 public void testUpdatePastEvents() { 1031 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1032 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 1033 long initialBeginTime = 1472864400000L; // Sep 3, 2016, 12AM UTC time 1034 mValues = new ContentValues(); 1035 1036 mModel1 = buildTestModel(); 1037 mModel1.mUri = (AUTHORITY_URI + TEST_EVENT_ID); 1038 mActivity = buildTestContext(); 1039 mHelper = new EditEventHelper(mActivity, null); 1040 1041 mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903;WKST=SU"); // yyyymmddThhmmssZ 1042 mValues.put(Events.DTSTART, TEST_START); 1043 1044 ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate( 1045 Uri.parse(mModel1.mUri)).withValues(mValues); 1046 expectedOps.add(b.build()); 1047 1048 mHelper.updatePastEvents(ops, mModel1, initialBeginTime); 1049 assertEquals(expectedOps, ops); 1050 1051 mModel1.mAllDay = false; 1052 1053 mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903T005959Z;WKST=SU"); // yyyymmddThhmmssZ 1054 1055 expectedOps.clear(); 1056 b = ContentProviderOperation.newUpdate(Uri.parse(mModel1.mUri)).withValues(mValues); 1057 expectedOps.add(b.build()); 1058 1059 ops.clear(); 1060 mHelper.updatePastEvents(ops, mModel1, initialBeginTime); 1061 assertEquals(expectedOps, ops); 1062 } 1063 1064 @Smoke 1065 @SmallTest 1066 public void testConstructReminderLabel() { 1067 mActivity = buildTestContext(); 1068 1069 String label = EventViewUtils.constructReminderLabel(mActivity, 35, true); 1070 assertEquals("35 mins", label); 1071 1072 label = EventViewUtils.constructReminderLabel(mActivity, 72, false); 1073 assertEquals("72 minutes", label); 1074 1075 label = EventViewUtils.constructReminderLabel(mActivity, 60, true); 1076 assertEquals("1 hours", label); 1077 1078 label = EventViewUtils.constructReminderLabel(mActivity, 60 * 48, true); 1079 assertEquals("2 days", label); 1080 } 1081 1082 @Smoke 1083 @SmallTest 1084 public void testIsSameEvent() { 1085 mModel1 = new CalendarEventModel(); 1086 mModel2 = new CalendarEventModel(); 1087 1088 mModel1.mId = 1; 1089 mModel1.mCalendarId = 1; 1090 mModel2.mId = 1; 1091 mModel2.mCalendarId = 1; 1092 1093 // considered the same if the event and calendar ids both match 1094 assertTrue(EditEventHelper.isSameEvent(mModel1, mModel2)); 1095 1096 mModel2.mId = 2; 1097 assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2)); 1098 1099 mModel2.mId = 1; 1100 mModel2.mCalendarId = 2; 1101 assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2)); 1102 } 1103 1104 @Smoke 1105 @SmallTest 1106 public void testSaveReminders() { 1107 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1108 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 1109 long eventId = TEST_EVENT_ID; 1110 ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>(); 1111 ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>(); 1112 boolean forceSave = true; 1113 boolean result; 1114 mActivity = buildTestContext(); 1115 mHelper = new EditEventHelper(mActivity, null); 1116 assertNotNull(mHelper); 1117 1118 // First test forcing a delete with no reminders. 1119 String where = Reminders.EVENT_ID + "=?"; 1120 String[] args = new String[] {Long.toString(eventId)}; 1121 ContentProviderOperation.Builder b = 1122 ContentProviderOperation.newDelete(Reminders.CONTENT_URI); 1123 b.withSelection(where, args); 1124 expectedOps.add(b.build()); 1125 1126 result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); 1127 assertTrue(result); 1128 assertEquals(expectedOps, ops); 1129 1130 // Now test calling save with identical reminders and no forcing 1131 reminders.add(ReminderEntry.valueOf(5)); 1132 reminders.add(ReminderEntry.valueOf(10)); 1133 reminders.add(ReminderEntry.valueOf(15)); 1134 1135 originalReminders.add(ReminderEntry.valueOf(5)); 1136 originalReminders.add(ReminderEntry.valueOf(10)); 1137 originalReminders.add(ReminderEntry.valueOf(15)); 1138 1139 forceSave = false; 1140 1141 ops.clear(); 1142 1143 // Should fail to create any ops since nothing changed 1144 result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); 1145 assertFalse(result); 1146 assertEquals(0, ops.size()); 1147 1148 //Now test adding a single reminder 1149 originalReminders.remove(2); 1150 1151 addExpectedMinutes(expectedOps); 1152 1153 result = mHelper.saveReminders(ops, eventId, reminders, originalReminders, forceSave); 1154 assertTrue(result); 1155 assertEquals(expectedOps, ops); 1156 } 1157 1158 @Smoke 1159 @SmallTest 1160 public void testSaveRemindersWithBackRef() { 1161 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1162 ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>(); 1163 long eventId = TEST_EVENT_ID; 1164 ArrayList<ReminderEntry> reminders = new ArrayList<ReminderEntry>(); 1165 ArrayList<ReminderEntry> originalReminders = new ArrayList<ReminderEntry>(); 1166 boolean forceSave = true; 1167 boolean result; 1168 mActivity = buildTestContext(); 1169 mHelper = new EditEventHelper(mActivity, null); 1170 assertNotNull(mHelper); 1171 1172 // First test forcing a delete with no reminders. 1173 ContentProviderOperation.Builder b = 1174 ContentProviderOperation.newDelete(Reminders.CONTENT_URI); 1175 b.withSelection(Reminders.EVENT_ID + "=?", new String[1]); 1176 b.withSelectionBackReference(0, TEST_EVENT_INDEX_ID); 1177 expectedOps.add(b.build()); 1178 1179 result = 1180 mHelper.saveRemindersWithBackRef(ops, TEST_EVENT_INDEX_ID, reminders, 1181 originalReminders, forceSave); 1182 assertTrue(result); 1183 assertEquals(expectedOps, ops); 1184 1185 // Now test calling save with identical reminders and no forcing 1186 reminders.add(ReminderEntry.valueOf(5)); 1187 reminders.add(ReminderEntry.valueOf(10)); 1188 reminders.add(ReminderEntry.valueOf(15)); 1189 1190 originalReminders.add(ReminderEntry.valueOf(5)); 1191 originalReminders.add(ReminderEntry.valueOf(10)); 1192 originalReminders.add(ReminderEntry.valueOf(15)); 1193 1194 forceSave = false; 1195 1196 ops.clear(); 1197 1198 result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders, 1199 forceSave); 1200 assertFalse(result); 1201 assertEquals(0, ops.size()); 1202 1203 //Now test adding a single reminder 1204 originalReminders.remove(2); 1205 1206 addExpectedMinutesWithBackRef(expectedOps); 1207 1208 result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminders, originalReminders, 1209 forceSave); 1210 assertTrue(result); 1211 assertEquals(expectedOps, ops); 1212 } 1213 1214 @Smoke 1215 @SmallTest 1216 public void testIsFirstEventInSeries() { 1217 mModel1 = new CalendarEventModel(); 1218 mModel2 = new CalendarEventModel(); 1219 1220 // It's considered the first event if the original start of the new model matches the 1221 // start of the old model 1222 mModel1.mOriginalStart = 100; 1223 mModel1.mStart = 200; 1224 mModel2.mOriginalStart = 100; 1225 mModel2.mStart = 100; 1226 1227 assertTrue(EditEventHelper.isFirstEventInSeries(mModel1, mModel2)); 1228 1229 mModel1.mOriginalStart = 80; 1230 assertFalse(EditEventHelper.isFirstEventInSeries(mModel1, mModel2)); 1231 } 1232 1233 @Smoke 1234 @SmallTest 1235 public void testAddRecurrenceRule() { 1236 mActivity = buildTestContext(); 1237 mHelper = new EditEventHelper(mActivity, null); 1238 mValues = new ContentValues(); 1239 mExpectedValues = new ContentValues(); 1240 mModel1 = new CalendarEventModel(); 1241 1242 mExpectedValues.put(Events.RRULE, "Weekly, Monday"); 1243 mExpectedValues.put(Events.DURATION, "P60S"); 1244 mExpectedValues.put(Events.DTEND, (Long) null); 1245 1246 mModel1.mRrule = "Weekly, Monday"; 1247 mModel1.mStart = 1; 1248 mModel1.mEnd = 60001; 1249 mModel1.mAllDay = false; 1250 1251 mHelper.addRecurrenceRule(mValues, mModel1); 1252 assertEquals(mExpectedValues, mValues); 1253 1254 mExpectedValues.put(Events.DURATION, "P1D"); 1255 1256 mModel1.mAllDay = true; 1257 mValues.clear(); 1258 1259 mHelper.addRecurrenceRule(mValues, mModel1); 1260 assertEquals(mExpectedValues, mValues); 1261 1262 } 1263 1264 @Smoke 1265 @SmallTest 1266 public void testUpdateRecurrenceRule() { 1267 int selection = EditEventHelper.DOES_NOT_REPEAT; 1268 int weekStart = Calendar.SUNDAY; 1269 mModel1 = new CalendarEventModel(); 1270 mModel1.mTimezone = Time.TIMEZONE_UTC; 1271 mModel1.mStart = 1272665741000L; // Fri, April 30th ~ 3:17PM 1272 1273 mModel1.mRrule = "This should go away"; 1274 1275 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1276 assertNull(mModel1.mRrule); 1277 1278 mModel1.mRrule = "This shouldn't change"; 1279 selection = EditEventHelper.REPEATS_CUSTOM; 1280 1281 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1282 assertEquals("This shouldn't change", mModel1.mRrule); 1283 1284 selection = EditEventHelper.REPEATS_DAILY; 1285 1286 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1287 assertEquals("FREQ=DAILY;WKST=SU", mModel1.mRrule); 1288 1289 selection = EditEventHelper.REPEATS_EVERY_WEEKDAY; 1290 1291 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1292 assertEquals("FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR", mModel1.mRrule); 1293 1294 selection = EditEventHelper.REPEATS_WEEKLY_ON_DAY; 1295 1296 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1297 assertEquals("FREQ=WEEKLY;WKST=SU;BYDAY=FR", mModel1.mRrule); 1298 1299 selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY; 1300 1301 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1302 assertEquals("FREQ=MONTHLY;WKST=SU;BYMONTHDAY=30", mModel1.mRrule); 1303 1304 selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT; 1305 1306 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1307 assertEquals("FREQ=MONTHLY;WKST=SU;BYDAY=-1FR", mModel1.mRrule); 1308 1309 selection = EditEventHelper.REPEATS_YEARLY; 1310 1311 EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart); 1312 assertEquals("FREQ=YEARLY;WKST=SU", mModel1.mRrule); 1313 } 1314 1315 @Smoke 1316 @SmallTest 1317 public void testSetModelFromCursor() { 1318 mActivity = buildTestContext(); 1319 mHelper = new EditEventHelper(mActivity, null); 1320 MatrixCursor c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); 1321 c.addRow(TEST_CURSOR_DATA); 1322 1323 mModel1 = new CalendarEventModel(); 1324 mModel2 = buildTestModel(); 1325 1326 EditEventHelper.setModelFromCursor(mModel1, c); 1327 assertEquals(mModel1, mModel2); 1328 1329 TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "0"; 1330 c.close(); 1331 c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); 1332 c.addRow(TEST_CURSOR_DATA); 1333 1334 mModel2.mAllDay = false; 1335 mModel2.mStart = TEST_START; // UTC time 1336 1337 EditEventHelper.setModelFromCursor(mModel1, c); 1338 assertEquals(mModel1, mModel2); 1339 1340 TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_RRULE] = null; 1341 c.close(); 1342 c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); 1343 c.addRow(TEST_CURSOR_DATA); 1344 1345 mModel2.mRrule = null; 1346 mModel2.mEnd = TEST_END; 1347 mModel2.mDuration = null; 1348 1349 EditEventHelper.setModelFromCursor(mModel1, c); 1350 assertEquals(mModel1, mModel2); 1351 1352 TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "1"; 1353 c.close(); 1354 c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION); 1355 c.addRow(TEST_CURSOR_DATA); 1356 1357 mModel2.mAllDay = true; 1358 mModel2.mStart = TEST_START; // Monday, May 3rd, midnight 1359 mModel2.mEnd = TEST_END; // Tuesday, May 4th, midnight 1360 1361 EditEventHelper.setModelFromCursor(mModel1, c); 1362 assertEquals(mModel1, mModel2); 1363 } 1364 1365 @Smoke 1366 @SmallTest 1367 public void testGetContentValuesFromModel() { 1368 mActivity = buildTestContext(); 1369 mHelper = new EditEventHelper(mActivity, null); 1370 mExpectedValues = buildTestValues(); 1371 mModel1 = buildTestModel(); 1372 1373 ContentValues values = mHelper.getContentValuesFromModel(mModel1); 1374 assertEquals(mExpectedValues, values); 1375 1376 mModel1.mRrule = null; 1377 mModel1.mEnd = TEST_END; 1378 1379 mExpectedValues.put(Events.RRULE, (String) null); 1380 mExpectedValues.put(Events.DURATION, (String) null); 1381 mExpectedValues.put(Events.DTEND, TEST_END); // UTC time 1382 1383 values = mHelper.getContentValuesFromModel(mModel1); 1384 assertEquals(mExpectedValues, values); 1385 1386 mModel1.mAllDay = false; 1387 1388 mExpectedValues.put(Events.ALL_DAY, 0); 1389 mExpectedValues.put(Events.DTSTART, TEST_START); 1390 mExpectedValues.put(Events.DTEND, TEST_END); 1391 // not an allday event so timezone isn't modified 1392 mExpectedValues.put(Events.EVENT_TIMEZONE, "UTC"); 1393 1394 values = mHelper.getContentValuesFromModel(mModel1); 1395 assertEquals(mExpectedValues, values); 1396 } 1397 1398 @Smoke 1399 @SmallTest 1400 public void testExtractDomain() { 1401 String domain = EditEventHelper.extractDomain("test.email@gmail.com"); 1402 assertEquals("gmail.com", domain); 1403 1404 domain = EditEventHelper.extractDomain("bademail.no#$%at symbol"); 1405 assertNull(domain); 1406 } 1407 1408 private void addExpectedMinutes(ArrayList<ContentProviderOperation> expectedOps) { 1409 ContentProviderOperation.Builder b; 1410 mValues = new ContentValues(); 1411 1412 mValues.clear(); 1413 mValues.put(Reminders.MINUTES, 5); 1414 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1415 mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); 1416 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1417 expectedOps.add(b.build()); 1418 1419 mValues.clear(); 1420 mValues.put(Reminders.MINUTES, 10); 1421 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1422 mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); 1423 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1424 expectedOps.add(b.build()); 1425 1426 mValues.clear(); 1427 mValues.put(Reminders.MINUTES, 15); 1428 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1429 mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID); 1430 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1431 expectedOps.add(b.build()); 1432 } 1433 1434 private void addExpectedMinutesWithBackRef(ArrayList<ContentProviderOperation> expectedOps) { 1435 ContentProviderOperation.Builder b; 1436 mValues = new ContentValues(); 1437 1438 mValues.clear(); 1439 mValues.put(Reminders.MINUTES, 5); 1440 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1441 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1442 b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); 1443 expectedOps.add(b.build()); 1444 1445 mValues.clear(); 1446 mValues.put(Reminders.MINUTES, 10); 1447 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1448 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1449 b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); 1450 expectedOps.add(b.build()); 1451 1452 mValues.clear(); 1453 mValues.put(Reminders.MINUTES, 15); 1454 mValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT); 1455 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues); 1456 b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID); 1457 expectedOps.add(b.build()); 1458 } 1459 1460 private static void assertEquals(ArrayList<ContentProviderOperation> expected, 1461 ArrayList<ContentProviderOperation> actual) { 1462 if (expected == null) { 1463 assertNull(actual); 1464 } 1465 int size = expected.size(); 1466 1467 assertEquals(size, actual.size()); 1468 1469 for (int i = 0; i < size; i++) { 1470 assertTrue("At index " + i + ", expected:\n" + String.valueOf(expected.get(i)) + 1471 "\nActual:\n" + String.valueOf(actual.get(i)), 1472 cpoEquals(expected.get(i), actual.get(i))); 1473 } 1474 1475 } 1476 1477 private static boolean cpoEquals(ContentProviderOperation cpo1, ContentProviderOperation cpo2) { 1478 if (cpo1 == null && cpo2 != null) { 1479 return false; 1480 } 1481 if (cpo1 == cpo2) { 1482 return true; 1483 } 1484 if (cpo2 == null) { 1485 return false; 1486 } 1487 1488 // It turns out we can't trust the toString() of the ContentProviderOperations to be 1489 // consistent, so we have to do the comparison manually. 1490 // 1491 // Start by splitting by commas, so that we can compare each key-value pair individually. 1492 String[] operations1 = cpo1.toString().split(","); 1493 String[] operations2 = cpo2.toString().split(","); 1494 // The two numbers of operations must be equal. 1495 if (operations1.length != operations2.length) { 1496 return false; 1497 } 1498 // Iterate through the key-value pairs and separate out the key and value. 1499 // The value may be either a single string, or a series of further key-value pairs 1500 // that are separated by " ", with a "=" between the key and value. 1501 for (int i = 0; i < operations1.length; i++) { 1502 String operation1 = operations1[i]; 1503 String operation2 = operations2[i]; 1504 // Limit the array to length 2 in case a ":" appears in the value. 1505 String[] keyValue1 = operation1.split(":", 2); 1506 String[] keyValue2 = operation2.split(":", 2); 1507 // If the key doesn't match, return false. 1508 if (!keyValue1[0].equals(keyValue2[0])) { 1509 return false; 1510 } 1511 // First just check if the value matches up. If so, we're good to go. 1512 if (keyValue1[1].equals(keyValue2[1])) { 1513 continue; 1514 } 1515 // If not, we need to try splitting the value by " " and sorting those keyvalue pairs. 1516 // Note that these are trimmed first to ensure we're not thrown off by extra whitespace. 1517 String[] valueKeyValuePairs1 = keyValue1[1].trim().split(" "); 1518 String[] valueKeyValuePairs2 = keyValue2[1].trim().split(" "); 1519 // Sort the value's keyvalue pairs alphabetically, and now compare them to each other. 1520 Arrays.sort(valueKeyValuePairs1); 1521 Arrays.sort(valueKeyValuePairs2); 1522 for (int j = 0; j < valueKeyValuePairs1.length; j++) { 1523 if (!valueKeyValuePairs1[j].equals(valueKeyValuePairs2[j])) { 1524 return false; 1525 } 1526 } 1527 } 1528 1529 // If we make it all the way through without finding anything different, return true. 1530 return true; 1531 } 1532 1533 // Generates a default model for testing. Should match up with 1534 // generateTestValues 1535 private CalendarEventModel buildTestModel() { 1536 CalendarEventModel model = new CalendarEventModel(); 1537 model.mId = TEST_EVENT_ID; 1538 model.mTitle = "The_Question"; 1539 model.mDescription = "Evaluating_Life_the_Universe_and_Everything"; 1540 model.mLocation = "Earth_Mk2"; 1541 model.mAllDay = true; 1542 model.mHasAlarm = false; 1543 model.mCalendarId = 2; 1544 model.mStart = TEST_START; // Monday, May 3rd, local Time 1545 model.mDuration = "P3652421990D"; 1546 // The model uses the local timezone for allday 1547 model.mTimezone = "UTC"; 1548 model.mRrule = "FREQ=DAILY;WKST=SU"; 1549 model.mSyncId = "unique_per_calendar_stuff"; 1550 model.mAvailability = 0; 1551 model.mAccessLevel = 2; // This is one less than the values written if >0 1552 model.mOwnerAccount = "steve@gmail.com"; 1553 model.mHasAttendeeData = true; 1554 model.mOrganizer = "organizer@gmail.com"; 1555 model.mIsOrganizer = false; 1556 model.mGuestsCanModify = false; 1557 model.mEventStatus = Events.STATUS_CONFIRMED; 1558 int displayColor = Utils.getDisplayColorFromColor(-2350809); 1559 model.setEventColor(displayColor); 1560 model.mCalendarAccountName = "steve.owner@gmail.com"; 1561 model.mCalendarAccountType = "gmail.com"; 1562 EventColorCache cache = new EventColorCache(); 1563 cache.insertColor("steve.owner@gmail.com", "gmail.com", displayColor, 12); 1564 model.mEventColorCache = cache; 1565 model.mModelUpdatedWithEventCursor = true; 1566 return model; 1567 } 1568 1569 // Generates a default set of values for testing. Should match up with 1570 // generateTestModel 1571 private ContentValues buildTestValues() { 1572 ContentValues values = new ContentValues(); 1573 1574 values.put(Events.CALENDAR_ID, 2L); 1575 values.put(Events.EVENT_TIMEZONE, "UTC"); // Allday events are converted 1576 // to UTC for the db 1577 values.put(Events.TITLE, "The_Question"); 1578 values.put(Events.ALL_DAY, 1); 1579 values.put(Events.DTSTART, TEST_START); // Monday, May 3rd, midnight UTC time 1580 values.put(Events.HAS_ATTENDEE_DATA, 1); 1581 1582 values.put(Events.RRULE, "FREQ=DAILY;WKST=SU"); 1583 values.put(Events.DURATION, "P3652421990D"); 1584 values.put(Events.DTEND, (Long) null); 1585 values.put(Events.DESCRIPTION, "Evaluating_Life_the_Universe_and_Everything"); 1586 values.put(Events.EVENT_LOCATION, "Earth_Mk2"); 1587 values.put(Events.AVAILABILITY, 0); 1588 values.put(Events.STATUS, Events.STATUS_CONFIRMED); 1589 values.put(Events.ACCESS_LEVEL, 3); // This is one more than the model if 1590 // >0 1591 values.put(Events.EVENT_COLOR_KEY, 12); 1592 1593 return values; 1594 } 1595 1596 private ContentValues buildNonRecurringTestValues() { 1597 ContentValues values = buildTestValues(); 1598 values.put(Events.DURATION, (String)null); 1599 values.put(Events.DTEND, TEST_END); 1600 values.put(Events.RRULE, (String)null); 1601 return values; 1602 } 1603 1604 // This gets called by EditEventHelper to read or write the data 1605 class TestProvider extends ContentProvider { 1606 int index = 0; 1607 1608 public TestProvider() { 1609 } 1610 1611 @Override 1612 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 1613 String orderBy) { 1614 return null; 1615 } 1616 1617 @Override 1618 public int delete(Uri uri, String selection, String[] selectionArgs) { 1619 return 0; 1620 } 1621 1622 @Override 1623 public String getType(Uri uri) { 1624 return null; 1625 } 1626 1627 @Override 1628 public boolean onCreate() { 1629 return false; 1630 } 1631 1632 @Override 1633 public Uri insert(Uri uri, ContentValues values) { 1634 // TODO Auto-generated method stub 1635 return null; 1636 } 1637 1638 @Override 1639 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1640 // TODO Auto-generated method stub 1641 return 0; 1642 } 1643 } 1644 1645} 1646