am a8b23f2: Refactor expandInstanceRangeLocked
Merge commit 'a8b23f2b386e3e0be20ef06522292314de8cbc78'
* commit 'a8b23f2b386e3e0be20ef06522292314de8cbc78':
Refactor expandInstanceRangeLocked
diff --git a/src/com/android/providers/calendar/CalendarProvider.java b/src/com/android/providers/calendar/CalendarProvider.java
index 0c0c082..d65b4fb 100644
--- a/src/com/android/providers/calendar/CalendarProvider.java
+++ b/src/com/android/providers/calendar/CalendarProvider.java
@@ -1671,95 +1671,122 @@
Debug.startMethodTracing("expandInstanceRangeLocked");
}
- final SQLiteDatabase db = getDatabase();
- Cursor entries = null;
-
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Expanding events between " + begin + " and " + end);
}
+ Cursor entries = getEntries(begin, end);
try {
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("Events INNER JOIN Calendars ON (calendar_id = Calendars._id)");
- qb.setProjectionMap(sEventsProjectionMap);
-
- String beginString = String.valueOf(begin);
- String endString = String.valueOf(end);
-
- qb.appendWhere("(dtstart <= ");
- qb.appendWhere(endString);
- qb.appendWhere(" AND ");
- qb.appendWhere("(lastDate IS NULL OR lastDate >= ");
- qb.appendWhere(beginString);
- qb.appendWhere(")) OR (");
- // grab recurrence exceptions that fall outside our expansion window but modify
- // recurrences that do fall within our window. we won't insert these into the output
- // set of instances, but instead will just add them to our cancellations list, so we
- // can cancel the correct recurrence expansion instances.
- qb.appendWhere("originalInstanceTime IS NOT NULL ");
- qb.appendWhere("AND originalInstanceTime <= ");
- qb.appendWhere(endString);
- qb.appendWhere(" AND ");
- // we don't have originalInstanceDuration or end time. for now, assume the original
- // instance lasts no longer than 1 week.
- // TODO: compute the originalInstanceEndTime or get this from the server.
- qb.appendWhere("originalInstanceTime >= ");
- qb.appendWhere(String.valueOf(begin - 7*24*60*60*1000 /* 1 week */));
- qb.appendWhere(")");
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Retrieving events to expand: " + qb.toString());
+ performInstanceExpansion(begin, end, localTimezone, entries);
+ } finally {
+ if (entries != null) {
+ entries.close();
}
+ }
+ if (PROFILE) {
+ Debug.stopMethodTracing();
+ }
+ }
- entries = qb.query(db, EXPAND_COLUMNS, null, null, null, null, null);
+ /**
+ * Get all entries affecting the given window.
+ * @param begin Window start (ms).
+ * @param end Window end (ms).
+ * @return Cursor for the entries; caller must close it.
+ */
+ private Cursor getEntries(long begin, long end) {
+ final SQLiteDatabase db = getDatabase();
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables("Events INNER JOIN Calendars ON (calendar_id = Calendars._id)");
+ qb.setProjectionMap(sEventsProjectionMap);
- RecurrenceProcessor rp = new RecurrenceProcessor();
+ String beginString = String.valueOf(begin);
+ String endString = String.valueOf(end);
- int statusColumn = entries.getColumnIndex(Events.STATUS);
- int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
- int dtendColumn = entries.getColumnIndex(Events.DTEND);
- int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
- int durationColumn = entries.getColumnIndex(Events.DURATION);
- int rruleColumn = entries.getColumnIndex(Events.RRULE);
- int rdateColumn = entries.getColumnIndex(Events.RDATE);
- int exruleColumn = entries.getColumnIndex(Events.EXRULE);
- int exdateColumn = entries.getColumnIndex(Events.EXDATE);
- int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
- int idColumn = entries.getColumnIndex(Events._ID);
- int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
- int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
- int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
+ qb.appendWhere("(dtstart <= ");
+ qb.appendWhere(endString);
+ qb.appendWhere(" AND ");
+ qb.appendWhere("(lastDate IS NULL OR lastDate >= ");
+ qb.appendWhere(beginString);
+ qb.appendWhere(")) OR (");
+ // grab recurrence exceptions that fall outside our expansion window but modify
+ // recurrences that do fall within our window. we won't insert these into the output
+ // set of instances, but instead will just add them to our cancellations list, so we
+ // can cancel the correct recurrence expansion instances.
+ qb.appendWhere("originalInstanceTime IS NOT NULL ");
+ qb.appendWhere("AND originalInstanceTime <= ");
+ qb.appendWhere(endString);
+ qb.appendWhere(" AND ");
+ // we don't have originalInstanceDuration or end time. for now, assume the original
+ // instance lasts no longer than 1 week.
+ // TODO: compute the originalInstanceEndTime or get this from the server.
+ qb.appendWhere("originalInstanceTime >= ");
+ qb.appendWhere(String.valueOf(begin - 7*24*60*60*1000 /* 1 week */));
+ qb.appendWhere(")");
- ContentValues initialValues;
- EventInstancesMap instancesMap = new EventInstancesMap();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Retrieving events to expand: " + qb.toString());
+ }
- Duration duration = new Duration();
- Time eventTime = new Time();
+ return qb.query(db, EXPAND_COLUMNS, null, null, null, null, null);
+ }
- // Invariant: entries contains all events that affect the current
- // window. It consists of:
- // a) Individual events that fall in the window. These will be
- // displayed.
- // b) Recurrences that included the window. These will be displayed
- // if not canceled.
- // c) Recurrence exceptions that fall in the window. These will be
- // displayed if not cancellations.
- // d) Recurrence exceptions that modify an instance inside the
- // window (subject to 1 week assumption above), but are outside
- // the window. These will not be displayed. Cases c and d are
- // distingushed by the start / end time.
+ /**
+ * Perform instance expansion on the given entries.
+ * @param begin Window start (ms).
+ * @param end Window end (ms).
+ * @param localTimezone
+ * @param entries The entries to process.
+ */
+ private void performInstanceExpansion(long begin, long end, String localTimezone, Cursor entries) {
+ RecurrenceProcessor rp = new RecurrenceProcessor();
- while (entries.moveToNext()) {
+ int statusColumn = entries.getColumnIndex(Events.STATUS);
+ int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
+ int dtendColumn = entries.getColumnIndex(Events.DTEND);
+ int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
+ int durationColumn = entries.getColumnIndex(Events.DURATION);
+ int rruleColumn = entries.getColumnIndex(Events.RRULE);
+ int rdateColumn = entries.getColumnIndex(Events.RDATE);
+ int exruleColumn = entries.getColumnIndex(Events.EXRULE);
+ int exdateColumn = entries.getColumnIndex(Events.EXDATE);
+ int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
+ int idColumn = entries.getColumnIndex(Events._ID);
+ int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
+ int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
+ int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
+
+ ContentValues initialValues;
+ EventInstancesMap instancesMap = new EventInstancesMap();
+
+ Duration duration = new Duration();
+ Time eventTime = new Time();
+
+ // Invariant: entries contains all events that affect the current
+ // window. It consists of:
+ // a) Individual events that fall in the window. These will be
+ // displayed.
+ // b) Recurrences that included the window. These will be displayed
+ // if not canceled.
+ // c) Recurrence exceptions that fall in the window. These will be
+ // displayed if not cancellations.
+ // d) Recurrence exceptions that modify an instance inside the
+ // window (subject to 1 week assumption above), but are outside
+ // the window. These will not be displayed. Cases c and d are
+ // distingushed by the start / end time.
+
+ while (entries.moveToNext()) {
+ try {
initialValues = null;
boolean allDay = entries.getInt(allDayColumn) != 0;
String eventTimezone = entries.getString(eventTimezoneColumn);
if (allDay || TextUtils.isEmpty(eventTimezone)) {
- // in the events table, allDay events start at midnight.
- // this forces them to stay at midnight for all day events
- // TODO: check that this actually does the right thing.
- eventTimezone = Time.TIMEZONE_UTC;
+ // in the events table, allDay events start at midnight.
+ // this forces them to stay at midnight for all day events
+ // TODO: check that this actually does the right thing.
+ eventTimezone = Time.TIMEZONE_UTC;
}
long dtstartMillis = entries.getLong(dtstartColumn);
@@ -1805,7 +1832,7 @@
if (status == Events.STATUS_CANCELED) {
// should not happen!
Log.e(TAG, "Found canceled recurring event in "
- + "Events table. Ignoring.");
+ + "Events table. Ignoring.");
continue;
}
@@ -1847,15 +1874,7 @@
}
long[] dates;
- try {
- dates = rp.expand(eventTime, recur, begin, end);
- } catch (DateException e) {
- Log.w(TAG, "RecurrenceProcessor.expand skipping " + recur, e);
- continue;
- } catch (TimeFormatException e) {
- Log.w(TAG, "RecurrenceProcessor.expand skipping " + recur, e);
- continue;
- }
+ dates = rp.expand(eventTime, recur, begin, end);
// Initialize the "eventTime" timezone outside the loop.
// This is used in computeTimezoneDependentFields().
@@ -1931,124 +1950,112 @@
instancesMap.add(syncId, initialValues);
}
+ } catch (DateException e) {
+ Log.w(TAG, "RecurrenceProcessor error ", e);
+ } catch (TimeFormatException e) {
+ Log.w(TAG, "RecurrenceProcessor error ", e);
}
+ }
- // Invariant: instancesMap contains all instances that affect the
- // window, indexed by original sync id. It consists of:
- // a) Individual events that fall in the window. They have:
- // EVENT_ID, BEGIN, END
- // b) Instances of recurrences that fall in the window. They may
- // be subject to exceptions. They have:
- // EVENT_ID, BEGIN, END
- // c) Exceptions that fall in the window. They have:
- // ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS (since they can
- // be a modification or cancellation), EVENT_ID, BEGIN, END
- // d) Recurrence exceptions that modify an instance inside the
- // window but fall outside the window. They have:
- // ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS =
- // STATUS_CANCELED, EVENT_ID, BEGIN, END
+ // Invariant: instancesMap contains all instances that affect the
+ // window, indexed by original sync id. It consists of:
+ // a) Individual events that fall in the window. They have:
+ // EVENT_ID, BEGIN, END
+ // b) Instances of recurrences that fall in the window. They may
+ // be subject to exceptions. They have:
+ // EVENT_ID, BEGIN, END
+ // c) Exceptions that fall in the window. They have:
+ // ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS (since they can
+ // be a modification or cancellation), EVENT_ID, BEGIN, END
+ // d) Recurrence exceptions that modify an instance inside the
+ // window but fall outside the window. They have:
+ // ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS =
+ // STATUS_CANCELED, EVENT_ID, BEGIN, END
- // First, delete the original instances corresponding to recurrence
- // exceptions. We do this by iterating over the list and for each
- // recurrence exception, we search the list for an instance with a
- // matching "original instance time". If we find such an instance,
- // we remove it from the list. If we don't find such an instance
- // then we cancel the recurrence exception.
- Set<String> keys = instancesMap.keySet();
- for (String syncId : keys) {
- InstancesList list = instancesMap.get(syncId);
- for (ContentValues values : list) {
+ // First, delete the original instances corresponding to recurrence
+ // exceptions. We do this by iterating over the list and for each
+ // recurrence exception, we search the list for an instance with a
+ // matching "original instance time". If we find such an instance,
+ // we remove it from the list. If we don't find such an instance
+ // then we cancel the recurrence exception.
+ Set<String> keys = instancesMap.keySet();
+ for (String syncId : keys) {
+ InstancesList list = instancesMap.get(syncId);
+ for (ContentValues values : list) {
- // If this instance is not a recurrence exception, then
- // skip it.
- if (!values.containsKey(Events.ORIGINAL_EVENT)) {
- continue;
- }
+ // If this instance is not a recurrence exception, then
+ // skip it.
+ if (!values.containsKey(Events.ORIGINAL_EVENT)) {
+ continue;
+ }
- String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
- long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
- InstancesList originalList = instancesMap.get(originalEvent);
- if (originalList == null) {
- // The original recurrence is not present, so don't try canceling it.
- continue;
- }
+ String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
+ long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
+ InstancesList originalList = instancesMap.get(originalEvent);
+ if (originalList == null) {
+ // The original recurrence is not present, so don't try canceling it.
+ continue;
+ }
- // Search the original event for a matching original
- // instance time. If there is a matching one, then remove
- // the original one. We do this both for exceptions that
- // change the original instance as well as for exceptions
- // that delete the original instance.
- boolean found = false;
- for (int num = originalList.size() - 1; num >= 0; num--) {
- ContentValues originalValues = originalList.get(num);
- long beginTime = originalValues.getAsLong(Instances.BEGIN);
- if (beginTime == originalTime) {
- // We found the original instance, so remove it.
- found = true;
- originalList.remove(num);
- }
- }
-
- // If we didn't find a matching instance time then cancel
- // this recurrence exception.
- if (!found) {
- values.put(Events.STATUS, Events.STATUS_CANCELED);
+ // Search the original event for a matching original
+ // instance time. If there is a matching one, then remove
+ // the original one. We do this both for exceptions that
+ // change the original instance as well as for exceptions
+ // that delete the original instance.
+ boolean found = false;
+ for (int num = originalList.size() - 1; num >= 0; num--) {
+ ContentValues originalValues = originalList.get(num);
+ long beginTime = originalValues.getAsLong(Instances.BEGIN);
+ if (beginTime == originalTime) {
+ // We found the original instance, so remove it.
+ found = true;
+ originalList.remove(num);
}
}
- }
- // Invariant: instancesMap contains filtered instances.
- // It consists of:
- // a) Individual events that fall in the window.
- // b) Instances of recurrences that fall in the window and have not
- // been subject to exceptions.
- // c) Exceptions that fall in the window. They will have
- // STATUS_CANCELED if they are cancellations.
- // d) Recurrence exceptions that modify an instance inside the
- // window but fall outside the window. These are STATUS_CANCELED.
-
- // Now do the inserts. Since the db lock is held when this method is executed,
- // this will be done in a transaction.
- // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
- // while the calendar app is trying to query the db (expanding instances)), we will
- // not be "polite" and yield the lock until we're done. This will favor local query
- // operations over sync/write operations.
- for (String syncId : keys) {
- InstancesList list = instancesMap.get(syncId);
- for (ContentValues values : list) {
-
- // If this instance was cancelled then don't create a new
- // instance.
- Integer status = values.getAsInteger(Events.STATUS);
- if (status != null && status == Events.STATUS_CANCELED) {
- continue;
- }
-
- // Remove these fields before inserting a new instance
- values.remove(Events.ORIGINAL_EVENT);
- values.remove(Events.ORIGINAL_INSTANCE_TIME);
- values.remove(Events.STATUS);
-
- mInstancesInserter.replace(values);
- if (false) {
- // yield the lock if anyone else is trying to
- // perform a db operation here.
- db.yieldIfContended();
- }
+ // If we didn't find a matching instance time then cancel
+ // this recurrence exception.
+ if (!found) {
+ values.put(Events.STATUS, Events.STATUS_CANCELED);
}
}
- } catch (TimeFormatException e) {
- Log.w(TAG, "Exception in instance query preparation", e);
}
- finally {
- if (entries != null) {
- entries.close();
+
+ // Invariant: instancesMap contains filtered instances.
+ // It consists of:
+ // a) Individual events that fall in the window.
+ // b) Instances of recurrences that fall in the window and have not
+ // been subject to exceptions.
+ // c) Exceptions that fall in the window. They will have
+ // STATUS_CANCELED if they are cancellations.
+ // d) Recurrence exceptions that modify an instance inside the
+ // window but fall outside the window. These are STATUS_CANCELED.
+
+ // Now do the inserts. Since the db lock is held when this method is executed,
+ // this will be done in a transaction.
+ // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
+ // while the calendar app is trying to query the db (expanding instances)), we will
+ // not be "polite" and yield the lock until we're done. This will favor local query
+ // operations over sync/write operations.
+ for (String syncId : keys) {
+ InstancesList list = instancesMap.get(syncId);
+ for (ContentValues values : list) {
+
+ // If this instance was cancelled then don't create a new
+ // instance.
+ Integer status = values.getAsInteger(Events.STATUS);
+ if (status != null && status == Events.STATUS_CANCELED) {
+ continue;
+ }
+
+ // Remove these fields before inserting a new instance
+ values.remove(Events.ORIGINAL_EVENT);
+ values.remove(Events.ORIGINAL_INSTANCE_TIME);
+ values.remove(Events.STATUS);
+
+ mInstancesInserter.replace(values);
}
}
- if (PROFILE) {
- Debug.stopMethodTracing();
- }
- //System.out.println("EXIT insertInstanceRange begin=" + begin + " end=" + end);
}
/**