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