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