CalendarSyncTestingBase.java revision b55344ddfebfccbdb07ed7a209e91819a5e9925d
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.calendar;
18
19import android.content.ContentResolver;
20import android.content.ContentUris;
21import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.net.Uri;
25import android.text.format.DateUtils;
26import android.text.format.Time;
27import android.provider.Calendar;
28import android.test.SyncBaseInstrumentation;
29import android.util.Log;
30import com.google.android.collect.Maps;
31import com.google.android.googlelogin.GoogleLoginServiceBlockingHelper;
32import com.google.android.googlelogin.GoogleLoginServiceNotFoundException;
33
34import java.util.HashSet;
35import java.util.Map;
36import java.util.Set;
37
38/**
39 * Calendar Sync base tests. Testing creation of setup, syncing calendar, inserting,
40 * deleting and editing events.
41 */
42public class CalendarSyncTestingBase extends SyncBaseInstrumentation {
43    protected Context mTargetContext;
44    protected String mAccount;
45    protected ContentResolver mResolver;
46    protected Uri mEventsUri = Uri.parse("content://calendar/events");
47
48    static final String TAG = "calendar";
49    static final String DEFAULT_TIMEZONE = "America/Los_Angeles";
50    static final Set<String> EVENT_COLUMNS_TO_SKIP = new HashSet<String>();
51    static final Set<String> ATTENDEES_COLUMNS_TO_SKIP = new HashSet<String>();
52    static final Set<String> CALENDARS_COLUMNS_TO_SKIP = new HashSet<String>();
53    static final Set<String> INSTANCES_COLUMNS_TO_SKIP = new HashSet<String>();
54
55    static {
56        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._ID);
57        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_TIME);
58        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_VERSION);
59        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_LOCAL_ID);
60        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_DIRTY);
61        EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_MARK);
62        ATTENDEES_COLUMNS_TO_SKIP.add(Calendar.Attendees._ID);
63        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._ID);
64        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_TIME);
65        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_VERSION);
66        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_LOCAL_ID);
67        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_DIRTY);
68        CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_MARK);
69        INSTANCES_COLUMNS_TO_SKIP.add(Calendar.Instances._ID);
70    }
71
72    @Override
73    protected void setUp() throws Exception {
74        super.setUp();
75        mTargetContext = getInstrumentation().getTargetContext();
76        mAccount = getAccount();
77        mResolver = mTargetContext.getContentResolver();
78    }
79
80    /**
81     * A simple method that syncs the calendar provider.
82     * @throws Exception
83     */
84    protected void syncCalendar() throws Exception {
85
86        cancelSyncsandDisableAutoSync();
87        syncProvider(Calendar.CONTENT_URI, mAccount, Calendar.AUTHORITY);
88    }
89
90    /**
91     * Creates a new event in the default calendar.
92     * @param event Event to be created.
93     * @return Uri of the created event.
94     * @throws Exception
95     */
96    protected Uri insertEvent(EventInfo event) throws Exception {
97        Log.i(TAG, "Get Dafault Calender Id");
98        return insertEvent(getDefaultCalendarId(), event);
99    }
100
101    /**
102     * Creates a new event in the given calendarId.
103     * @param calendarId Calendar to be used.
104     * @param event Event to be created.
105     * @return Uri of the event created.
106     * @throws Exception
107     */
108    protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{
109        ContentValues m = new ContentValues();
110        m.put(Calendar.Events.CALENDAR_ID, calendarId);
111        m.put(Calendar.Events.TITLE, event.mTitle);
112        m.put(Calendar.Events.DTSTART, event.mDtstart);
113        m.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0);
114
115        if (event.mRrule == null) {
116            // This is a normal event
117            m.put(Calendar.Events.DTEND, event.mDtend);
118        } else {
119            // This is a repeating event
120            m.put(Calendar.Events.RRULE, event.mRrule);
121            m.put(Calendar.Events.DURATION, event.mDuration);
122        }
123
124        if (event.mDescription != null) {
125            m.put(Calendar.Events.DESCRIPTION, event.mDescription);
126        }
127        if (event.mTimezone != null) {
128            m.put(Calendar.Events.EVENT_TIMEZONE, event.mTimezone);
129        }
130
131        Log.v(TAG, "Test Calendar Event");
132        Uri url = mResolver.insert(mEventsUri, m);
133        Log.v(TAG, "Insert Calendar Event");
134        syncCalendar();
135        Log.v(TAG, "Test Sync Calendar");
136        return url;
137    }
138
139    /**
140     * Edits the given event.
141     * @param eventId EventID of the event to be edited.
142     * @param event Edited event details.
143     * @throws Exception
144     */
145    protected void editEvent(long eventId, EventInfo event) throws Exception {
146        ContentValues values = new ContentValues();
147        values.put(Calendar.Events.TITLE, event.mTitle);
148        values.put(Calendar.Events.DTSTART, event.mDtstart);
149        values.put(Calendar.Events.DTEND, event.mDtend);
150        values.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0);
151
152        if (event.mDescription != null) {
153            values.put(Calendar.Events.DESCRIPTION, event.mDescription);
154        } else if(event.mLocation != null) {
155            values.put(Calendar.Events.EVENT_LOCATION, event.mLocation);
156        } else if(event.mTitle != null) {
157            values.put(Calendar.Events.TITLE, event.mTitle);
158        }
159        else {
160            throw new RuntimeException("Unsupported: description or location must be specified");
161        }
162
163        Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
164        mResolver.update(uri, values, null, null);
165        syncCalendar();
166    }
167
168    /**
169     * Deletes a given event.
170     * @param uri
171     * @throws Exception
172     */
173    protected void deleteEvent(Uri uri) throws Exception {
174        mResolver.delete(uri, null, null);
175        syncCalendar();
176    }
177
178    /**
179     * Inserts a new calendar.
180     * @param name
181     * @param timezone
182     * @param calendarUrl
183     * @throws Exception
184     */
185    protected void insertCalendar(String name, String timezone, String calendarUrl)
186            throws Exception {
187        ContentValues values = new ContentValues();
188
189        values.put(Calendar.Calendars._SYNC_ACCOUNT, getAccount());
190        values.put(Calendar.Calendars.URL, calendarUrl);
191        values.put(Calendar.Calendars.NAME, name);
192        values.put(Calendar.Calendars.DISPLAY_NAME, name);
193        values.put(Calendar.Calendars.SYNC_EVENTS, 1);
194        values.put(Calendar.Calendars.SELECTED, 1);
195        values.put(Calendar.Calendars.HIDDEN, 0);
196        values.put(Calendar.Calendars.COLOR, "0xff123456" /* blue */);
197        values.put(Calendar.Calendars.ACCESS_LEVEL, Calendar.Calendars.OWNER_ACCESS);
198        values.put(Calendar.Calendars.TIMEZONE, timezone);
199        mResolver.insert(Calendar.Calendars.CONTENT_URI, values);
200        syncCalendar();
201    }
202
203    /**
204     * Returns a fresh count of events.
205     * @return
206     */
207    protected int getEventsCount() {
208        Cursor cursor;
209        cursor = mResolver.query(mEventsUri, null, null, null, null);
210        return cursor.getCount();
211    }
212
213    /**
214     * Returns the ID of the default calendar.
215     * @return
216     */
217    protected int getDefaultCalendarId() {
218        Cursor calendarsCursor;
219        calendarsCursor = mResolver.query(Calendar.Calendars.CONTENT_URI, null, null, null, null);
220        calendarsCursor.moveToNext();
221        return calendarsCursor.getInt(calendarsCursor.getColumnIndex("_id"));
222    }
223
224    /**
225     * This class stores all the useful information about an event.
226     */
227    protected class EventInfo {
228        String mTitle;
229        String mDescription;
230        String mLocation;
231        String mTimezone;
232        boolean mAllDay;
233        long mDtstart;
234        long mDtend;
235        String mRrule;
236        String mDuration;
237        String mOriginalTitle;
238        long mOriginalInstance;
239        int mSyncId;
240
241        // Constructor for normal events, using the default timezone
242        public EventInfo(String title, String startDate, String endDate,
243                boolean allDay) {
244            init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE);
245        }
246
247        public EventInfo(String title, long startDate, long endDate,
248                boolean allDay) {
249            mTitle = title;
250            mTimezone = DEFAULT_TIMEZONE;
251            mDtstart = startDate;
252            mDtend = endDate;
253            mDuration = null;
254            mRrule = null;
255            mAllDay = allDay;
256        }
257
258        public EventInfo(String title, long startDate, long endDate,
259                boolean allDay, String description) {
260            mTitle = title;
261            mTimezone = DEFAULT_TIMEZONE;
262            mDtstart = startDate;
263            mDtend = endDate;
264            mDuration = null;
265            mRrule = null;
266            mAllDay = allDay;
267            mDescription = description;
268        }
269
270
271        // Constructor for normal events, specifying the timezone
272        public EventInfo(String title, String startDate, String endDate,
273                boolean allDay, String timezone) {
274            init(title, startDate, endDate, allDay, timezone);
275        }
276
277        public void init(String title, String startDate, String endDate,
278                boolean allDay, String timezone) {
279            mTitle = title;
280            Time time = new Time();
281            if (allDay) {
282                time.timezone = Time.TIMEZONE_UTC;
283            } else if (timezone != null) {
284                time.timezone = timezone;
285            }
286            mTimezone = time.timezone;
287            time.parse3339(startDate);
288            mDtstart = time.toMillis(false /* use isDst */);
289            time.parse3339(endDate);
290            mDtend = time.toMillis(false /* use isDst */);
291            mDuration = null;
292            mRrule = null;
293            mAllDay = allDay;
294        }
295
296        /**
297         * Constructor for repeating events, using the default time zone.
298         * @param title
299         * @param description
300         * @param startDate
301         * @param endDate
302         * @param rrule
303         * @param allDay
304         */
305        public EventInfo(String title, String description, String startDate, String endDate,
306                String rrule, boolean allDay) {
307            init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE);
308        }
309
310        /**
311         * Constructor for repeating events, using the specific time zone.
312         * @param title
313         * @param description
314         * @param startDate
315         * @param endDate
316         * @param rrule
317         * @param allDay
318         * @param timezone
319         */
320        public EventInfo(String title, String description, String startDate, String endDate,
321                String rrule, boolean allDay, String timezone) {
322            init(title, description, startDate, endDate, rrule, allDay, timezone);
323        }
324
325        public void init(String title, String description, String startDate, String endDate,
326                String rrule, boolean allDay, String timezone) {
327            mTitle = title;
328            mDescription = description;
329            Time time = new Time();
330            if (allDay) {
331                time.timezone = Time.TIMEZONE_UTC;
332            } else if (timezone != null) {
333                time.timezone = timezone;
334            }
335            mTimezone = time.timezone;
336            time.parse3339(startDate);
337            mDtstart = time.toMillis(false /* use isDst */);
338            if (endDate != null) {
339                time.parse3339(endDate);
340                mDtend = time.toMillis(false /* use isDst */);
341            }
342            if (allDay) {
343                long days = 1;
344                if (endDate != null) {
345                    days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS;
346                }
347                mDuration = "P" + days + "D";
348            } else {
349                long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS;
350                mDuration = "P" + seconds + "S";
351            }
352            mRrule = rrule;
353            mAllDay = allDay;
354        }
355
356        // Constructor for recurrence exceptions, using the default timezone
357        public EventInfo(String originalTitle, String originalInstance, String title,
358                String description, String startDate, String endDate, boolean allDay) {
359            init(originalTitle, originalInstance,
360                    title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE);
361        }
362
363        public void init(String originalTitle, String originalInstance,
364                String title, String description, String startDate, String endDate,
365                boolean allDay, String timezone) {
366            mOriginalTitle = originalTitle;
367            Time time = new Time(timezone);
368            time.parse3339(originalInstance);
369            mOriginalInstance = time.toMillis(false /* use isDst */);
370            init(title, description, startDate, endDate, null /* rrule */, allDay, timezone);
371        }
372    }
373
374    /**
375     * Returns the default account on the device.
376     * @return
377     */
378    protected String getAccount() {
379        try {
380            return GoogleLoginServiceBlockingHelper.getAccount(mTargetContext, false);
381        } catch (GoogleLoginServiceNotFoundException e) {
382            Log.e("SyncCalendarTest", "Could not find Google login service", e);
383            return null;
384        }
385    }
386
387    /**
388     * Compares two cursors
389     */
390    protected void compareCursors(Cursor cursor1, Cursor cursor2,
391                                  Set<String> columnsToSkip, String tableName) {
392        String[] cols = cursor1.getColumnNames();
393        int length = cols.length;
394
395        assertEquals(tableName + " count failed to match", cursor1.getCount(),
396                cursor2.getCount());
397        Map<String, String> row = Maps.newHashMap();
398        while (cursor1.moveToNext() && cursor2.moveToNext()) {
399            for (int i = 0; i < length; i++) {
400                String col = cols[i];
401                if (columnsToSkip != null && columnsToSkip.contains(col)) {
402                    continue;
403                }
404                row.put(col, cursor1.getString(i));
405
406                assertEquals("Row: " + row + " Table: " + tableName + ": " + cols[i] +
407                        " failed to match", cursor1.getString(i),
408                        cursor2.getString(i));
409            }
410        }
411    }
412}
413