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();
+    }
+}