Add original_id column + logic. db -> v301
Adds an original_id to the Events table for local reference to
an original event for exceptions. It also adds logic to keep this
value in sync with the original_sync_id so apps don't have to
update for it to continue working.
Change-Id: I0034ecedb6f7b582ba823daab8656b7778781d84
diff --git a/src/com/android/providers/calendar/CalendarDatabaseHelper.java b/src/com/android/providers/calendar/CalendarDatabaseHelper.java
index fa8627a..da1b355 100644
--- a/src/com/android/providers/calendar/CalendarDatabaseHelper.java
+++ b/src/com/android/providers/calendar/CalendarDatabaseHelper.java
@@ -32,6 +32,7 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.provider.Calendar;
+import android.provider.Calendar.Events;
import android.provider.ContactsContract;
import android.provider.SyncStateContract;
import android.text.TextUtils;
@@ -61,7 +62,7 @@
// Versions under 100 cover through Froyo, 1xx version are for Gingerbread,
// 2xx for Honeycomb, and 3xx for ICS. For future versions bump this to the
// next hundred at each major release.
- static final int DATABASE_VERSION = 300;
+ static final int DATABASE_VERSION = 301;
private static final int PRE_FROYO_SYNC_STATE_VERSION = 3;
@@ -108,6 +109,21 @@
" WHERE " + Calendar.ExtendedProperties.EVENT_ID + "=" +
"old." + Calendar.Events._ID + ";";
+ // This ensures any exceptions based on an event get their original_sync_id
+ // column set when an the _sync_id is set.
+ private static final String EVENTS_ORIGINAL_SYNC_TRIGGER_SQL =
+ "UPDATE " + Tables.EVENTS +
+ " SET " + Events.ORIGINAL_SYNC_ID + "=new." + Events._SYNC_ID +
+ " WHERE " + Events.ORIGINAL_ID + "=old." + Events._ID + ";";
+
+ private static final String SYNC_ID_UPDATE_TRIGGER_NAME = "original_sync_update";
+ private static final String CREATE_SYNC_ID_UPDATE_TRIGGER =
+ "CREATE TRIGGER " + SYNC_ID_UPDATE_TRIGGER_NAME + " UPDATE OF " + Events._SYNC_ID +
+ " ON " + Tables.EVENTS +
+ " BEGIN " +
+ EVENTS_ORIGINAL_SYNC_TRIGGER_SQL +
+ " END";
+
private static final String CALENDAR_CLEANUP_TRIGGER_SQL = "DELETE FROM " + Tables.EVENTS +
" WHERE " + Calendar.Events.CALENDAR_ID + "=" +
"old." + Calendar.Events._ID + ";";
@@ -363,6 +379,10 @@
EVENTS_CLEANUP_TRIGGER_SQL +
"END");
+ // Trigger to update exceptions when an original event updates its
+ // _sync_id
+ db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
+
ContentResolver.requestSync(null /* all accounts */,
ContactsContract.AUTHORITY, new Bundle());
}
@@ -403,6 +423,65 @@
Calendar.Events.RDATE + " TEXT," +
Calendar.Events.EXRULE + " TEXT," +
Calendar.Events.EXDATE + " TEXT," +
+ Calendar.Events.ORIGINAL_ID + " INTEGER," +
+ // ORIGINAL_SYNC_ID is the _sync_id of recurring event
+ Calendar.Events.ORIGINAL_SYNC_ID + " TEXT," +
+ // originalInstanceTime is in millis since epoch
+ Calendar.Events.ORIGINAL_INSTANCE_TIME + " INTEGER," +
+ Calendar.Events.ORIGINAL_ALL_DAY + " INTEGER," +
+ // lastDate is in millis since epoch
+ Calendar.Events.LAST_DATE + " INTEGER," +
+ Calendar.Events.HAS_ATTENDEE_DATA + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.GUESTS_CAN_MODIFY + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.GUESTS_CAN_INVITE_OTHERS + " INTEGER NOT NULL DEFAULT 1," +
+ Calendar.Events.GUESTS_CAN_SEE_GUESTS + " INTEGER NOT NULL DEFAULT 1," +
+ Calendar.Events.ORGANIZER + " STRING," +
+ Calendar.Events.DELETED + " INTEGER NOT NULL DEFAULT 0," +
+ // timezone for event with allDay events are in local timezone
+ Calendar.Events.EVENT_END_TIMEZONE + " TEXT," +
+ // SYNC_DATA1 is available for use by sync adapters
+ Calendar.Events.SYNC_DATA1 + " TEXT" + ");");
+
+ db.execSQL("CREATE INDEX eventsCalendarIdIndex ON " + Tables.EVENTS + " ("
+ + Calendar.Events.CALENDAR_ID + ");");
+ }
+
+ // TODO Remove this method after merging all ICS upgrades
+ private void createEventsTable300(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + Tables.EVENTS + " (" +
+ Calendar.Events._ID + " INTEGER PRIMARY KEY," +
+ Calendar.Events._SYNC_ID + " TEXT," +
+ Calendar.Events._SYNC_VERSION + " TEXT," +
+ // sync time in UTC
+ Calendar.Events._SYNC_TIME + " TEXT," +
+ Calendar.Events._SYNC_DATA + " INTEGER," +
+ Calendar.Events.DIRTY + " INTEGER," +
+ // sync mark to filter out new rows
+ Calendar.Events._SYNC_MARK + " INTEGER," +
+ Calendar.Events.CALENDAR_ID + " INTEGER NOT NULL," +
+ Calendar.Events.HTML_URI + " TEXT," +
+ Calendar.Events.TITLE + " TEXT," +
+ Calendar.Events.EVENT_LOCATION + " TEXT," +
+ Calendar.Events.DESCRIPTION + " TEXT," +
+ Calendar.Events.STATUS + " INTEGER," +
+ Calendar.Events.SELF_ATTENDEE_STATUS + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.COMMENTS_URI + " TEXT," +
+ // dtstart in millis since epoch
+ Calendar.Events.DTSTART + " INTEGER," +
+ // dtend in millis since epoch
+ Calendar.Events.DTEND + " INTEGER," +
+ // timezone for event
+ Calendar.Events.EVENT_TIMEZONE + " TEXT," +
+ Calendar.Events.DURATION + " TEXT," +
+ Calendar.Events.ALL_DAY + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.ACCESS_LEVEL + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.AVAILABILITY + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.HAS_ALARM + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.HAS_EXTENDED_PROPERTIES + " INTEGER NOT NULL DEFAULT 0," +
+ Calendar.Events.RRULE + " TEXT," +
+ Calendar.Events.RDATE + " TEXT," +
+ Calendar.Events.EXRULE + " TEXT," +
+ Calendar.Events.EXDATE + " TEXT," +
// originalEvent is the _sync_id of recurring event
Calendar.Events.ORIGINAL_SYNC_ID + " TEXT," +
// originalInstanceTime is in millis since epoch
@@ -845,6 +924,11 @@
createEventsView = true;
oldVersion = 300;
}
+ if (oldVersion == 300) {
+ upgradeToVersion301(db);
+ createEventsView = true;
+ oldVersion++;
+ }
if (createEventsView) {
createEventsView(db);
}
@@ -895,6 +979,28 @@
}
@VisibleForTesting
+ void upgradeToVersion301(SQLiteDatabase db) {
+ /*
+ * Changes from version 300 to 301
+ * - Added original_id column to Events table
+ * - Added triggers to keep original_id and original_sync_id in sync
+ */
+
+ db.execSQL("DROP TRIGGER IF EXISTS " + SYNC_ID_UPDATE_TRIGGER_NAME + ";");
+
+ db.execSQL("ALTER TABLE Events ADD COLUMN original_id INTEGER;");
+
+ // Fill in the original_id for all events that have an original_sync_id
+ db.execSQL("UPDATE Events set original_id=" +
+ "(SELECT Events2._id FROM Events AS Events2 " +
+ "WHERE Events2._sync_id=Events.original_sync_id) " +
+ "WHERE Events.original_sync_id NOT NULL");
+ // Trigger to update exceptions when an original event updates its
+ // _sync_id
+ db.execSQL(CREATE_SYNC_ID_UPDATE_TRIGGER);
+ }
+
+ @VisibleForTesting
void upgradeToVersion300(SQLiteDatabase db) {
/*
@@ -917,7 +1023,7 @@
// rename old table, create new table with updated layout
db.execSQL("ALTER TABLE Calendars RENAME TO Calendars_Backup;");
- db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
+ db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup;");
createCalendarsTable(db);
// copy fields from old to new
@@ -989,7 +1095,7 @@
db.execSQL("DROP TRIGGER IF EXISTS events_cleanup_delete");
db.execSQL("DROP INDEX IF EXISTS eventSyncAccountAndIdIndex");
db.execSQL("DROP INDEX IF EXISTS eventsCalendarIdIndex");
- createEventsTable(db);
+ createEventsTable300(db);
// copy fields from old to new
db.execSQL("INSERT INTO Events (" +
@@ -2025,6 +2131,7 @@
+ Calendar.Events.EXRULE + ","
+ Calendar.Events.EXDATE + ","
+ Calendar.Events.ORIGINAL_SYNC_ID + ","
+ + Calendar.Events.ORIGINAL_ID + ","
+ Calendar.Events.ORIGINAL_INSTANCE_TIME + ","
+ Calendar.Events.ORIGINAL_ALL_DAY + ","
+ Calendar.Events.LAST_DATE + ","
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index 783632a..5255d4a 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -1431,6 +1431,21 @@
updatedValues.put(Events.ORGANIZER, owner);
}
}
+ if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
+ && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
+ long originalId = getOriginalId(updatedValues
+ .getAsString(Events.ORIGINAL_SYNC_ID));
+ if (originalId != -1) {
+ updatedValues.put(Events.ORIGINAL_ID, originalId);
+ }
+ } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
+ && updatedValues.containsKey(Events.ORIGINAL_ID)) {
+ String originalSyncId = getOriginalSyncId(updatedValues
+ .getAsLong(Events.ORIGINAL_ID));
+ if (!TextUtils.isEmpty(originalSyncId)) {
+ updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
+ }
+ }
if (fixAllDayTime(uri, updatedValues)) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "insertInTransaction: " +
@@ -1485,6 +1500,20 @@
}
}
}
+ // If this was a recurring event with a _sync_id update any
+ // exceptions that may have been added prior to the original
+ // event
+ if (values.containsKey(Events._SYNC_ID) && values.containsKey(Events.RRULE)
+ && !TextUtils.isEmpty(values.getAsString(Events.RRULE))) {
+ String syncId = values.getAsString(Events._SYNC_ID);
+ if (TextUtils.isEmpty(syncId)) {
+ break;
+ }
+ ContentValues originalValues = new ContentValues();
+ originalValues.put(Events.ORIGINAL_ID, id);
+ mDb.update(Tables.EVENTS, originalValues, Events.ORIGINAL_SYNC_ID + "=?",
+ new String[] {syncId});
+ }
sendUpdateNotification(id, callerIsSyncAdapter);
}
break;
@@ -1639,6 +1668,48 @@
mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Integer[] {eventId});
}
+ private long getOriginalId(String originalSyncId) {
+ if (TextUtils.isEmpty(originalSyncId)) {
+ return -1;
+ }
+ // Get the original id for this event
+ long originalId = -1;
+ Cursor c = null;
+ try {
+ c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
+ Events._SYNC_ID + "=?", new String[] {originalSyncId}, null);
+ if (c != null && c.moveToFirst()) {
+ originalId = c.getLong(0);
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return originalId;
+ }
+
+ private String getOriginalSyncId(long originalId) {
+ if (originalId == -1) {
+ return null;
+ }
+ // Get the original id for this event
+ String originalSyncId = null;
+ Cursor c = null;
+ try {
+ c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
+ Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
+ if (c != null && c.moveToFirst()) {
+ originalSyncId = c.getString(0);
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return originalSyncId;
+ }
+
/**
* Gets the calendar's owner for an event.
* @param calId
@@ -3099,6 +3170,7 @@
sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
+ sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
@@ -3162,6 +3234,7 @@
sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
+ sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
Events.ORIGINAL_INSTANCE_TIME);
sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
diff --git a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
index a2fdc89..dafad3a 100644
--- a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
+++ b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
@@ -1913,11 +1913,11 @@
cursor.close();
}
-
/**
- * Test the event's _sync_dirty status and clear it.
+ * Test the event's dirty status and clear it.
+ *
* @param eventId event to fetch.
- * @param wanted the wanted _sync_dirty status
+ * @param wanted the wanted dirty status
*/
private void testAndClearDirty(long eventId, int wanted) {
Cursor cursor = mResolver.query(
@@ -1930,7 +1930,7 @@
assertEquals("dirty flag", wanted, dirty);
if (dirty == 1) {
// Have to access database directly since provider will set dirty again.
- mDb.execSQL("UPDATE Events SET _sync_dirty=0 WHERE _id=" + eventId);
+ mDb.execSQL("UPDATE Events SET " + Events.DIRTY + "=0 WHERE _id=" + eventId);
}
} finally {
cursor.close();