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