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