• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.providers.calendar;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.SystemClock;
28 import android.provider.CalendarContract;
29 import android.test.SyncBaseInstrumentation;
30 import android.text.format.DateUtils;
31 import android.text.format.Time;
32 import android.util.Log;
33 
34 import com.google.android.collect.Maps;
35 
36 import java.util.HashSet;
37 import java.util.Map;
38 import java.util.Set;
39 
40 public class CalendarSyncTestingBase extends SyncBaseInstrumentation {
41     protected AccountManager mAccountManager;
42     protected Context mTargetContext;
43     protected String mAccount;
44     protected ContentResolver mResolver;
45     protected Uri mEventsUri = CalendarContract.Events.CONTENT_URI;
46 
47     static final String TAG = "calendar";
48     static final String DEFAULT_TIMEZONE = "America/Los_Angeles";
49     static final Set<String> EVENT_COLUMNS_TO_SKIP = new HashSet<String>();
50     static final Set<String> ATTENDEES_COLUMNS_TO_SKIP = new HashSet<String>();
51     static final Set<String> CALENDARS_COLUMNS_TO_SKIP = new HashSet<String>();
52     static final Set<String> INSTANCES_COLUMNS_TO_SKIP = new HashSet<String>();
53 
54     static {
55         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events._ID);
56         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA5);
57         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA4);
58         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA2);
59         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.DIRTY);
60         EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA8);
61         ATTENDEES_COLUMNS_TO_SKIP.add(CalendarContract.Attendees._ID);
62         CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars._ID);
63         CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC8);
64         CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC7);
65         CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.DIRTY);
66         CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC6);
67         INSTANCES_COLUMNS_TO_SKIP.add(CalendarContract.Instances._ID);
68     }
69 
70     @Override
setUp()71     protected void setUp() throws Exception {
72         super.setUp();
73         mTargetContext = getInstrumentation().getTargetContext();
74 
75         mAccountManager = AccountManager.get(mTargetContext);
76         mAccount = getAccount();
77         mResolver = mTargetContext.getContentResolver();
78     }
79 
80     /**
81      * A simple method that syncs the calendar provider.
82      * @throws Exception
83      */
syncCalendar()84     protected void syncCalendar() throws Exception {
85         cancelSyncsandDisableAutoSync();
86         syncProvider(CalendarContract.CONTENT_URI, mAccount, CalendarContract.AUTHORITY);
87     }
88 
89     /**
90      * Creates a new event in the default calendar.
91      * @param event Event to be created.
92      * @return Uri of the created event.
93      * @throws Exception
94      */
insertEvent(EventInfo event)95     protected Uri insertEvent(EventInfo event) throws Exception {
96         return insertEvent(getDefaultCalendarId(), event);
97     }
98 
99     /**
100      * Creates a new event in the given calendarId.
101      * @param calendarId Calendar to be used.
102      * @param event Event to be created.
103      * @return Uri of the event created.
104      * @throws Exception
105      */
insertEvent(int calendarId, EventInfo event)106     protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{
107         ContentValues m = new ContentValues();
108         m.put(CalendarContract.Events.CALENDAR_ID, calendarId);
109         m.put(CalendarContract.Events.TITLE, event.mTitle);
110         m.put(CalendarContract.Events.DTSTART, event.mDtstart);
111         m.put(CalendarContract.Events.ALL_DAY, event.mAllDay ? 1 : 0);
112 
113         if (event.mRrule == null) {
114             // This is a normal event
115             m.put(CalendarContract.Events.DTEND, event.mDtend);
116         } else {
117             // This is a repeating event
118             m.put(CalendarContract.Events.RRULE, event.mRrule);
119             m.put(CalendarContract.Events.DURATION, event.mDuration);
120         }
121 
122         if (event.mDescription != null) {
123             m.put(CalendarContract.Events.DESCRIPTION, event.mDescription);
124         }
125         if (event.mTimezone != null) {
126             m.put(CalendarContract.Events.EVENT_TIMEZONE, event.mTimezone);
127         }
128 
129         Uri url = mResolver.insert(mEventsUri, m);
130         syncCalendar();
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      */
editEvent(long eventId, EventInfo event)140     protected void editEvent(long eventId, EventInfo event) throws Exception {
141         ContentValues values = new ContentValues();
142         values.put(CalendarContract.Events.TITLE, event.mTitle);
143         values.put(CalendarContract.Events.DTSTART, event.mDtstart);
144         values.put(CalendarContract.Events.DTEND, event.mDtend);
145         values.put(CalendarContract.Events.ALL_DAY, event.mAllDay ? 1 : 0);
146 
147         if (event.mDescription != null) {
148             values.put(CalendarContract.Events.DESCRIPTION, event.mDescription);
149         }
150 
151         Uri uri = ContentUris.withAppendedId(CalendarContract.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      */
deleteEvent(Uri uri)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      */
insertCalendar(String name, String timezone, String calendarUrl)173     protected void insertCalendar(String name, String timezone, String calendarUrl)
174             throws Exception {
175         ContentValues values = new ContentValues();
176 
177         values.put(CalendarContract.Calendars.ACCOUNT_NAME, getAccount());
178         values.put(CalendarContract.Calendars.CAL_SYNC1, calendarUrl);
179         values.put(CalendarContract.Calendars.NAME, name);
180         values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name);
181 
182         values.put(CalendarContract.Calendars.SYNC_EVENTS, 1);
183         values.put(CalendarContract.Calendars.VISIBLE, 1);
184         values.put(CalendarContract.Calendars.CALENDAR_COLOR, -14069085 /* blue */);
185         values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL,
186                 CalendarContract.Calendars.CAL_ACCESS_OWNER);
187 
188         values.put(CalendarContract.Calendars.CALENDAR_COLOR, "0xff123456");
189         values.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, timezone);
190         mResolver.insert(CalendarContract.Calendars.CONTENT_URI, values);
191         syncCalendar();
192     }
193 
194     /**
195      * Returns a fresh count of events.
196      * @return
197      */
getEventsCount()198     protected int getEventsCount() {
199         Cursor cursor;
200         cursor = mResolver.query(mEventsUri, null, null, null, null);
201         return cursor.getCount();
202     }
203 
204     /**
205      * Returns the ID of the default calendar.
206      * @return
207      */
getDefaultCalendarId()208     protected int getDefaultCalendarId() {
209         Cursor calendarsCursor;
210         calendarsCursor = mResolver.query(CalendarContract.Calendars.CONTENT_URI, null, null, null,
211                 null);
212         calendarsCursor.moveToNext();
213         return calendarsCursor.getInt(calendarsCursor.getColumnIndex("_id"));
214     }
215 
216     /**
217      * This class stores all the useful information about an event.
218      */
219     protected class EventInfo {
220         String mTitle;
221         String mDescription;
222         String mTimezone;
223         boolean mAllDay;
224         long mDtstart;
225         long mDtend;
226         String mRrule;
227         String mDuration;
228         String mOriginalTitle;
229         long mOriginalInstance;
230         int mSyncId;
231 
232         // Constructor for normal events, using the default timezone
EventInfo(String title, String startDate, String endDate, boolean allDay)233         public EventInfo(String title, String startDate, String endDate,
234                 boolean allDay) {
235             init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE);
236         }
237 
EventInfo(String title, long startDate, long endDate, boolean allDay)238         public EventInfo(String title, long startDate, long endDate,
239                 boolean allDay) {
240             mTitle = title;
241             mTimezone = DEFAULT_TIMEZONE;
242             mDtstart = startDate;
243             mDtend = endDate;
244             mDuration = null;
245             mRrule = null;
246             mAllDay = allDay;
247         }
248 
EventInfo(String title, long startDate, long endDate, boolean allDay, String description)249         public EventInfo(String title, long startDate, long endDate,
250                 boolean allDay, String description) {
251             mTitle = title;
252             mTimezone = DEFAULT_TIMEZONE;
253             mDtstart = startDate;
254             mDtend = endDate;
255             mDuration = null;
256             mRrule = null;
257             mAllDay = allDay;
258             mDescription = description;
259         }
260 
261         // Constructor for normal events, specifying the timezone
EventInfo(String title, String startDate, String endDate, boolean allDay, String timezone)262         public EventInfo(String title, String startDate, String endDate,
263                 boolean allDay, String timezone) {
264             init(title, startDate, endDate, allDay, timezone);
265         }
266 
init(String title, String startDate, String endDate, boolean allDay, String timezone)267         public void init(String title, String startDate, String endDate,
268                 boolean allDay, String timezone) {
269             mTitle = title;
270             Time time = new Time();
271             if (allDay) {
272                 time.timezone = Time.TIMEZONE_UTC;
273             } else if (timezone != null) {
274                 time.timezone = timezone;
275             }
276             mTimezone = time.timezone;
277             time.parse3339(startDate);
278             mDtstart = time.toMillis(false /* use isDst */);
279             time.parse3339(endDate);
280             mDtend = time.toMillis(false /* use isDst */);
281             mDuration = null;
282             mRrule = null;
283             mAllDay = allDay;
284         }
285 
286         // Constructor for repeating events, using the default timezone
EventInfo(String title, String description, String startDate, String endDate, String rrule, boolean allDay)287         public EventInfo(String title, String description, String startDate, String endDate,
288                 String rrule, boolean allDay) {
289             init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE);
290         }
291 
292         // Constructor for repeating events, specifying the timezone
EventInfo(String title, String description, String startDate, String endDate, String rrule, boolean allDay, String timezone)293         public EventInfo(String title, String description, String startDate, String endDate,
294                 String rrule, boolean allDay, String timezone) {
295             init(title, description, startDate, endDate, rrule, allDay, timezone);
296         }
297 
init(String title, String description, String startDate, String endDate, String rrule, boolean allDay, String timezone)298         public void init(String title, String description, String startDate, String endDate,
299                 String rrule, boolean allDay, String timezone) {
300             mTitle = title;
301             mDescription = description;
302             Time time = new Time();
303             if (allDay) {
304                 time.timezone = Time.TIMEZONE_UTC;
305             } else if (timezone != null) {
306                 time.timezone = timezone;
307             }
308             mTimezone = time.timezone;
309             time.parse3339(startDate);
310             mDtstart = time.toMillis(false /* use isDst */);
311             if (endDate != null) {
312                 time.parse3339(endDate);
313                 mDtend = time.toMillis(false /* use isDst */);
314             }
315             if (allDay) {
316                 long days = 1;
317                 if (endDate != null) {
318                     days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS;
319                 }
320                 mDuration = "P" + days + "D";
321             } else {
322                 long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS;
323                 mDuration = "P" + seconds + "S";
324             }
325             mRrule = rrule;
326             mAllDay = allDay;
327         }
328 
329         // Constructor for recurrence exceptions, using the default timezone
EventInfo(String originalTitle, String originalInstance, String title, String description, String startDate, String endDate, boolean allDay)330         public EventInfo(String originalTitle, String originalInstance, String title,
331                 String description, String startDate, String endDate, boolean allDay) {
332             init(originalTitle, originalInstance,
333                     title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE);
334         }
335 
init(String originalTitle, String originalInstance, String title, String description, String startDate, String endDate, boolean allDay, String timezone)336         public void init(String originalTitle, String originalInstance,
337                 String title, String description, String startDate, String endDate,
338                 boolean allDay, String timezone) {
339             mOriginalTitle = originalTitle;
340             Time time = new Time(timezone);
341             time.parse3339(originalInstance);
342             mOriginalInstance = time.toMillis(false /* use isDst */);
343             init(title, description, startDate, endDate, null /* rrule */, allDay, timezone);
344         }
345     }
346 
347     /**
348      * Returns the default account on the device.
349      * @return
350      */
getAccount()351     protected String getAccount() {
352         Account[] accounts = mAccountManager.getAccountsByType("com.google");
353 
354         assertTrue("Didn't find any Google accounts", accounts.length > 0);
355 
356         Account account = accounts[accounts.length - 1];
357         Log.v(TAG, "Found " + accounts.length + " accounts; using the last one, " + account.name);
358         return account.name;
359     }
360 
361     /**
362      * Compares two cursors
363      */
compareCursors(Cursor cursor1, Cursor cursor2, Set<String> columnsToSkip, String tableName)364     protected void compareCursors(Cursor cursor1, Cursor cursor2,
365                                   Set<String> columnsToSkip, String tableName) {
366         String[] cols = cursor1.getColumnNames();
367         int length = cols.length;
368 
369         assertEquals(tableName + " count failed to match", cursor1.getCount(),
370                 cursor2.getCount());
371         Map<String, String> row = Maps.newHashMap();
372         while (cursor1.moveToNext() && cursor2.moveToNext()) {
373             for (int i = 0; i < length; i++) {
374                 String col = cols[i];
375                 if (columnsToSkip != null && columnsToSkip.contains(col)) {
376                     continue;
377                 }
378                 row.put(col, cursor1.getString(i));
379 
380                 assertEquals("Row: " + row + " Table: " + tableName + ": " + cols[i] +
381                         " failed to match", cursor1.getString(i),
382                         cursor2.getString(i));
383             }
384         }
385     }
386 }
387