Port SyncCalendar Tests from the 1.0 release tree to Donut Client.
diff --git a/Android.mk b/Android.mk
index 3d7c95c..ed34da1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,7 +22,7 @@
# We depend on googlelogin-client also, but that is already
# being included by google-framework
-LOCAL_STATIC_JAVA_LIBRARIES := google-framework
+LOCAL_STATIC_JAVA_LIBRARIES := google-framework googlelogin-client
LOCAL_PACKAGE_NAME := CalendarProvider
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 700096a..7f53f11 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,6 +28,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" />
<uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
<application android:process="com.android.calendar"
android:label="Calendar Storage"
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 137e511..4856613 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -24,6 +24,9 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
<!--
The test delcared in this instrumentation will be run along with tests declared by
diff --git a/tests/src/com/android/providers/calendar/CalendarSyncTestingBase.java b/tests/src/com/android/providers/calendar/CalendarSyncTestingBase.java
new file mode 100644
index 0000000..be852e9
--- /dev/null
+++ b/tests/src/com/android/providers/calendar/CalendarSyncTestingBase.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.calendar;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.provider.Calendar;
+import android.test.SyncBaseInstrumentation;
+import android.util.Log;
+import com.google.android.collect.Maps;
+import com.google.android.googlelogin.GoogleLoginServiceBlockingHelper;
+import com.google.android.googlelogin.GoogleLoginServiceNotFoundException;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class CalendarSyncTestingBase extends SyncBaseInstrumentation {
+ protected Context mTargetContext;
+ protected String mAccount;
+ protected ContentResolver mResolver;
+ protected Uri mEventsUri = Uri.parse("content://calendar/events");
+
+ static final String TAG = "calendar";
+ static final String DEFAULT_TIMEZONE = "America/Los_Angeles";
+ static final Set<String> EVENT_COLUMNS_TO_SKIP = new HashSet<String>();
+ static final Set<String> ATTENDEES_COLUMNS_TO_SKIP = new HashSet<String>();
+ static final Set<String> CALENDARS_COLUMNS_TO_SKIP = new HashSet<String>();
+ static final Set<String> INSTANCES_COLUMNS_TO_SKIP = new HashSet<String>();
+
+ static {
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._ID);
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_TIME);
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_VERSION);
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_LOCAL_ID);
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_DIRTY);
+ EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_MARK);
+ ATTENDEES_COLUMNS_TO_SKIP.add(Calendar.Attendees._ID);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._ID);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_TIME);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_VERSION);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_LOCAL_ID);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_DIRTY);
+ CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_MARK);
+ INSTANCES_COLUMNS_TO_SKIP.add(Calendar.Instances._ID);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTargetContext = getInstrumentation().getTargetContext();
+ mAccount = getAccount();
+ mResolver = mTargetContext.getContentResolver();
+ }
+
+ /**
+ * A simple method that syncs the calendar provider.
+ * @throws Exception
+ */
+ protected void syncCalendar() throws Exception {
+ cancelSyncsandDisableAutoSync();
+ syncProvider(Calendar.CONTENT_URI, mAccount, Calendar.AUTHORITY);
+ }
+
+ /**
+ * Creates a new event in the default calendar.
+ * @param event Event to be created.
+ * @return Uri of the created event.
+ * @throws Exception
+ */
+ protected Uri insertEvent(EventInfo event) throws Exception {
+ Log.v("FOO", "Get Default Calendar Id");
+ return insertEvent(getDefaultCalendarId(), event);
+ }
+
+ /**
+ * Creates a new event in the given calendarId.
+ * @param calendarId Calendar to be used.
+ * @param event Event to be created.
+ * @return Uri of the event created.
+ * @throws Exception
+ */
+ protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{
+ ContentValues m = new ContentValues();
+ m.put(Calendar.Events.CALENDAR_ID, calendarId);
+ m.put(Calendar.Events.TITLE, event.mTitle);
+ m.put(Calendar.Events.DTSTART, event.mDtstart);
+ m.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0);
+
+ if (event.mRrule == null) {
+ // This is a normal event
+ m.put(Calendar.Events.DTEND, event.mDtend);
+ } else {
+ // This is a repeating event
+ m.put(Calendar.Events.RRULE, event.mRrule);
+ m.put(Calendar.Events.DURATION, event.mDuration);
+ }
+
+ if (event.mDescription != null) {
+ m.put(Calendar.Events.DESCRIPTION, event.mDescription);
+ }
+ if (event.mTimezone != null) {
+ m.put(Calendar.Events.EVENT_TIMEZONE, event.mTimezone);
+ }
+
+ Log.v("FOO", "Test Calendar Event");
+ Uri url = mResolver.insert(mEventsUri, m);
+ Log.v("FOO", "Insert Calendar Event");
+ syncCalendar();
+ Log.v("FOO", "Test Sync Calendar");
+ return url;
+ }
+
+ /**
+ * Edits the given event.
+ * @param eventId EventID of the event to be edited.
+ * @param event Edited event details.
+ * @throws Exception
+ */
+ protected void editEvent(long eventId, EventInfo event) throws Exception {
+ ContentValues values = new ContentValues();
+ values.put(Calendar.Events.TITLE, event.mTitle);
+ values.put(Calendar.Events.DTSTART, event.mDtstart);
+ values.put(Calendar.Events.DTEND, event.mDtend);
+ values.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0);
+
+ if (event.mDescription != null) {
+ values.put(Calendar.Events.DESCRIPTION, event.mDescription);
+ }
+
+ Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
+ mResolver.update(uri, values, null, null);
+ syncCalendar();
+ }
+
+ /**
+ * Deletes a given event.
+ * @param uri
+ * @throws Exception
+ */
+ protected void deleteEvent(Uri uri) throws Exception {
+ mResolver.delete(uri, null, null);
+ syncCalendar();
+ }
+
+ /**
+ * Inserts a new calendar.
+ * @param name
+ * @param timezone
+ * @param calendarUrl
+ * @throws Exception
+ */
+ protected void insertCalendar(String name, String timezone, String calendarUrl)
+ throws Exception {
+ ContentValues values = new ContentValues();
+
+ values.put(Calendar.Calendars._SYNC_ACCOUNT, getAccount());
+ values.put(Calendar.Calendars.URL, calendarUrl);
+ values.put(Calendar.Calendars.NAME, name);
+ values.put(Calendar.Calendars.DISPLAY_NAME, name);
+ values.put(Calendar.Calendars.SYNC_EVENTS, 1);
+ values.put(Calendar.Calendars.SELECTED, 1);
+ values.put(Calendar.Calendars.HIDDEN, 0);
+ values.put(Calendar.Calendars.COLOR, "0xff123456" /* blue */);
+ values.put(Calendar.Calendars.ACCESS_LEVEL, Calendar.Calendars.OWNER_ACCESS);
+ values.put(Calendar.Calendars.TIMEZONE, timezone);
+ mResolver.insert(Calendar.Calendars.CONTENT_URI, values);
+ syncCalendar();
+ }
+
+ /**
+ * Returns a fresh count of events.
+ * @return
+ */
+ protected int getEventsCount() {
+ Cursor cursor;
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+ return cursor.getCount();
+ }
+
+ /**
+ * Returns the ID of the default calendar.
+ * @return
+ */
+ protected int getDefaultCalendarId() {
+ Cursor calendarsCursor;
+ calendarsCursor = mResolver.query(Calendar.Calendars.CONTENT_URI, null, null, null, null);
+ calendarsCursor.moveToNext();
+ return calendarsCursor.getInt(calendarsCursor.getColumnIndex("_id"));
+ }
+
+ /**
+ * This class stores all the useful information about an event.
+ */
+ protected class EventInfo {
+ String mTitle;
+ String mDescription;
+ String mTimezone;
+ boolean mAllDay;
+ long mDtstart;
+ long mDtend;
+ String mRrule;
+ String mDuration;
+ String mOriginalTitle;
+ long mOriginalInstance;
+ int mSyncId;
+
+ // Constructor for normal events, using the default timezone
+ public EventInfo(String title, String startDate, String endDate,
+ boolean allDay) {
+ init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE);
+ }
+
+ public EventInfo(String title, long startDate, long endDate,
+ boolean allDay) {
+ mTitle = title;
+ mTimezone = DEFAULT_TIMEZONE;
+ mDtstart = startDate;
+ mDtend = endDate;
+ mDuration = null;
+ mRrule = null;
+ mAllDay = allDay;
+ }
+
+ public EventInfo(String title, long startDate, long endDate,
+ boolean allDay, String description) {
+ mTitle = title;
+ mTimezone = DEFAULT_TIMEZONE;
+ mDtstart = startDate;
+ mDtend = endDate;
+ mDuration = null;
+ mRrule = null;
+ mAllDay = allDay;
+ mDescription = description;
+ }
+
+ // Constructor for normal events, specifying the timezone
+ public EventInfo(String title, String startDate, String endDate,
+ boolean allDay, String timezone) {
+ init(title, startDate, endDate, allDay, timezone);
+ }
+
+ public void init(String title, String startDate, String endDate,
+ boolean allDay, String timezone) {
+ mTitle = title;
+ Time time = new Time();
+ if (allDay) {
+ time.timezone = Time.TIMEZONE_UTC;
+ } else if (timezone != null) {
+ time.timezone = timezone;
+ }
+ mTimezone = time.timezone;
+ time.parse3339(startDate);
+ mDtstart = time.toMillis(false /* use isDst */);
+ time.parse3339(endDate);
+ mDtend = time.toMillis(false /* use isDst */);
+ mDuration = null;
+ mRrule = null;
+ mAllDay = allDay;
+ }
+
+ /**
+ * Constructor for repeating events, using the default time zone.
+ * @param title
+ * @param description
+ * @param startDate
+ * @param endDate
+ * @param rrule
+ * @param allDay
+ */
+ public EventInfo(String title, String description, String startDate, String endDate,
+ String rrule, boolean allDay) {
+ init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE);
+ }
+
+ /**
+ * Constructor for repeating events, using the specific time zone.
+ * @param title
+ * @param description
+ * @param startDate
+ * @param endDate
+ * @param rrule
+ * @param allDay
+ * @param timezone
+ */
+ public EventInfo(String title, String description, String startDate, String endDate,
+ String rrule, boolean allDay, String timezone) {
+ init(title, description, startDate, endDate, rrule, allDay, timezone);
+ }
+
+ public void init(String title, String description, String startDate, String endDate,
+ String rrule, boolean allDay, String timezone) {
+ mTitle = title;
+ mDescription = description;
+ Time time = new Time();
+ if (allDay) {
+ time.timezone = Time.TIMEZONE_UTC;
+ } else if (timezone != null) {
+ time.timezone = timezone;
+ }
+ mTimezone = time.timezone;
+ time.parse3339(startDate);
+ mDtstart = time.toMillis(false /* use isDst */);
+ if (endDate != null) {
+ time.parse3339(endDate);
+ mDtend = time.toMillis(false /* use isDst */);
+ }
+ if (allDay) {
+ long days = 1;
+ if (endDate != null) {
+ days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS;
+ }
+ mDuration = "P" + days + "D";
+ } else {
+ long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS;
+ mDuration = "P" + seconds + "S";
+ }
+ mRrule = rrule;
+ mAllDay = allDay;
+ }
+
+ // Constructor for recurrence exceptions, using the default timezone
+ public EventInfo(String originalTitle, String originalInstance, String title,
+ String description, String startDate, String endDate, boolean allDay) {
+ init(originalTitle, originalInstance,
+ title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE);
+ }
+
+ public void init(String originalTitle, String originalInstance,
+ String title, String description, String startDate, String endDate,
+ boolean allDay, String timezone) {
+ mOriginalTitle = originalTitle;
+ Time time = new Time(timezone);
+ time.parse3339(originalInstance);
+ mOriginalInstance = time.toMillis(false /* use isDst */);
+ init(title, description, startDate, endDate, null /* rrule */, allDay, timezone);
+ }
+ }
+
+ /**
+ * Returns the default account on the device.
+ * @return
+ */
+ protected String getAccount() {
+ try {
+ return GoogleLoginServiceBlockingHelper.getAccount(mTargetContext, false);
+ } catch (GoogleLoginServiceNotFoundException e) {
+ Log.e("SyncCalendarTest", "Could not find Google login service", e);
+ return null;
+ }
+ }
+
+ /**
+ * Compares two cursors
+ */
+ protected void compareCursors(Cursor cursor1, Cursor cursor2,
+ Set<String> columnsToSkip, String tableName) {
+ String[] cols = cursor1.getColumnNames();
+ int length = cols.length;
+
+ assertEquals(tableName + " count failed to match", cursor1.getCount(),
+ cursor2.getCount());
+ Map<String, String> row = Maps.newHashMap();
+ while (cursor1.moveToNext() && cursor2.moveToNext()) {
+ for (int i = 0; i < length; i++) {
+ String col = cols[i];
+ if (columnsToSkip != null && columnsToSkip.contains(col)) {
+ continue;
+ }
+ row.put(col, cursor1.getString(i));
+
+ assertEquals("Row: " + row + " Table: " + tableName + ": " + cols[i] +
+ " failed to match", cursor1.getString(i),
+ cursor2.getString(i));
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/providers/calendar/SyncCalendarTest.java b/tests/src/com/android/providers/calendar/SyncCalendarTest.java
new file mode 100644
index 0000000..5fbd2d5
--- /dev/null
+++ b/tests/src/com/android/providers/calendar/SyncCalendarTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.calendar;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.format.Time;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import android.util.Log;
+
+/**
+ * Calendar Sync instrumentation tests. Testing creation of new events, deleting events,
+ * editing events.
+ */
+public class SyncCalendarTest extends CalendarSyncTestingBase {
+ private EventInfo normalEvent =
+ new EventInfo("normal0", "2009-05-29T08:00:00", "2009-05-29T08:30:00", false);
+ private EventInfo dailyRecurringEvent = new EventInfo("dailyEvent",
+ "daily from 5/1/2009 12am to 1am", "2009-10-01T00:00:00", "2009-10-01T01:00:00",
+ "FREQ=DAILY;WKST=SU", false);
+
+ private static final long ONE_HOUR_IN_MILLIS = 3600000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @LargeTest
+ public void testCreateNewEvent() throws Exception {
+ int countBeforeNewEvent = getEventsCount();
+ insertEvent(normalEvent);
+ assertTrue("No new event was added. ", getEventsCount() > countBeforeNewEvent);
+ }
+
+ @LargeTest
+ public void testCreateAndDeleteNewRecurringEvent() throws Exception {
+ int countBeforeNewEvent = getEventsCount();
+ Uri insertUri = insertEvent(dailyRecurringEvent);
+
+ assertTrue("A daily recurring event should have been created.",
+ getEventsCount() > countBeforeNewEvent);
+ deleteEvent(insertUri);
+ assertEquals("Daily recurring event should have been deleted.",
+ countBeforeNewEvent, getEventsCount());
+ }
+
+ @LargeTest
+ public void testCreateAllDayEvent() throws Exception {
+ Time time = new Time();
+ time.setToNow();
+ long dtStart = time.toMillis(false);
+ long dtEnd = time.toMillis(false) + ONE_HOUR_IN_MILLIS;
+ EventInfo allDayEvent = new EventInfo("allday0", dtStart, dtEnd, true);
+
+ Log.v("FOO", "alpha");
+ int countBeforeNewEvent = getEventsCount();
+ Log.v("FOO", "beta");
+ insertEvent(allDayEvent);
+ Log.v("FOO", "gamma");
+
+ assertTrue("An all-day event should have been created.",
+ getEventsCount() > countBeforeNewEvent);
+ Log.v("FOO", "delta");
+ }
+
+ @LargeTest
+ public void testEditEventTitle() throws Exception {
+ Cursor cursor;
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+
+ int countBeforeNewEvent = cursor.getCount();
+ cursor.moveToNext();
+ Time time = new Time();
+ time.setToNow();
+ String newTitle = cursor.getString(cursor.getColumnIndex("title")) + time.toString();
+ long dtStart = cursor.getLong(cursor.getColumnIndex("dtstart"));
+ long dtEnd = cursor.getLong(cursor.getColumnIndex("dtend"));
+
+ EventInfo event = new EventInfo(newTitle, dtStart, dtEnd, false);
+ long eventId = cursor.getLong(cursor.getColumnIndex("_id"));
+
+ editEvent(eventId, event);
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+ assertTrue("Events count should remain same.", getEventsCount() == countBeforeNewEvent);
+
+ while (cursor.moveToNext()) {
+ if (cursor.getLong(cursor.getColumnIndex("_id")) == eventId) {
+ assertEquals(cursor.getString(cursor.getColumnIndex("title")), newTitle);
+ break;
+ }
+ }
+ cursor.close();
+ }
+
+ @LargeTest
+ public void testEditEventDate() throws Exception {
+ Cursor cursor;
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+
+ int countBeforeNewEvent = cursor.getCount();
+ cursor.moveToNext();
+ Time time = new Time();
+ String title = cursor.getString(cursor.getColumnIndex("title"));
+ long dtStart = cursor.getLong(cursor.getColumnIndex("dtstart"));
+ time.set(dtStart + 2 * ONE_HOUR_IN_MILLIS);
+ long newDtStart = time.toMillis(false);
+ time.set(dtStart + 3 * ONE_HOUR_IN_MILLIS);
+ long newDtEnd = time.toMillis(false);
+
+ EventInfo event = new EventInfo(title, newDtStart, newDtEnd, false);
+ long eventId = cursor.getLong(cursor.getColumnIndex("_id"));
+
+ editEvent(eventId, event);
+
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+ int countAfterNewEvent = cursor.getCount();
+ assertTrue("Events count should remain same.", countAfterNewEvent == countBeforeNewEvent);
+
+ while (cursor.moveToNext()){
+ if (cursor.getLong(cursor.getColumnIndex("_id")) == eventId) {
+ System.err.println("startindex: " + cursor.getColumnIndex("dtstart"));
+ System.err.println(" endindex: " + cursor.getColumnIndex("dtend"));
+ assertEquals(cursor.getLong(cursor.getColumnIndex("dtstart")), newDtStart);
+ assertEquals(cursor.getLong(cursor.getColumnIndex("dtend")), newDtEnd);
+ break;
+ }
+ }
+ cursor.close();
+ }
+
+ @LargeTest
+ public void testEditEventDescription() throws Exception {
+ Cursor cursor;
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+ int countBeforeNewEvent = cursor.getCount();
+ cursor.moveToNext();
+
+ String title = cursor.getString(cursor.getColumnIndex("title"));
+ long dtStart = cursor.getLong(cursor.getColumnIndex("dtstart"));
+ long dtEnd = cursor.getLong(cursor.getColumnIndex("dtend"));
+
+ String newDescription = "NEW Descrption";
+ EventInfo event = new EventInfo(title, dtStart, dtEnd, false, newDescription);
+
+ long eventId = cursor.getLong(cursor.getColumnIndex("_id"));
+ editEvent(eventId, event);
+
+ cursor = mResolver.query(mEventsUri, null, null, null, null);
+ int countAfterNewEvent = cursor.getCount();
+ assertTrue("Events count should remain same.", countAfterNewEvent == countBeforeNewEvent);
+
+ while (cursor.moveToNext()){
+ if (cursor.getLong(cursor.getColumnIndex("_id")) == eventId) {
+ assertEquals(cursor.getString(cursor.getColumnIndex("description")), newDescription);
+ break;
+ }
+ }
+ cursor.close();
+ }
+}