1f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik/* 2f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Copyright (C) 2011 The Android Open Source Project 3f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 4f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Licensed under the Apache License, Version 2.0 (the "License"); 5f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * you may not use this file except in compliance with the License. 6f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * You may obtain a copy of the License at 7f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 8f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * http://www.apache.org/licenses/LICENSE-2.0 9f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 10f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Unless required by applicable law or agreed to in writing, software 11f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * distributed under the License is distributed on an "AS IS" BASIS, 12f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * See the License for the specific language governing permissions and 14f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * limitations under the License. 15f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 16f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 17f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikpackage com.android.providers.calendar; 18f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 195cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.DateException; 205cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.Duration; 215cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.EventRecurrence; 225cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.RecurrenceProcessor; 235cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.RecurrenceSet; 24f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables; 2593e0bbb921cce7a5cec355521bc570c03c9d6a1cAndy McFadden 26f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.content.ContentValues; 27f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.database.Cursor; 28f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.database.DatabaseUtils; 29f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.database.sqlite.SQLiteDatabase; 30f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.database.sqlite.SQLiteQueryBuilder; 31f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.os.Debug; 32b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars; 33b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events; 34b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances; 35f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.text.TextUtils; 36f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.text.format.Time; 37f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.util.Log; 38f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport android.util.TimeFormatException; 39f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 40f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport java.util.ArrayList; 41f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport java.util.HashMap; 42f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikimport java.util.Set; 43f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 44f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErikpublic class CalendarInstancesHelper { 45f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik public static final class EventInstancesMap extends 46f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik HashMap<String, CalendarInstancesHelper.InstancesList> { 47f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik public void add(String syncIdKey, ContentValues values) { 48f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.InstancesList instances = get(syncIdKey); 49f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (instances == null) { 50f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instances = new CalendarInstancesHelper.InstancesList(); 51f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik put(syncIdKey, instances); 52f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 53f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instances.add(values); 54f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 55f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 56f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 57f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik public static final class InstancesList extends ArrayList<ContentValues> { 58f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 59f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 60f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final String TAG = "CalInstances"; 61f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private CalendarDatabaseHelper mDbHelper; 62f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private SQLiteDatabase mDb; 63f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private MetaData mMetaData; 64f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private CalendarCache mCalendarCache; 65f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 66f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final String SQL_WHERE_GET_EVENTS_ENTRIES = 67f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik "((" + Events.DTSTART + " <= ? AND " 68f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + "(" + Events.LAST_DATE + " IS NULL OR " + Events.LAST_DATE + " >= ?)) OR " 69f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + "(" + Events.ORIGINAL_INSTANCE_TIME + " IS NOT NULL AND " 70f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + Events.ORIGINAL_INSTANCE_TIME 71f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + " <= ? AND " + Events.ORIGINAL_INSTANCE_TIME + " >= ?)) AND " 729ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert + "(" + Calendars.SYNC_EVENTS + " != ?) AND " 739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert + "(" + Events.LAST_SYNCED + " = ?)"; 74f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 75b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /** 76b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Determines the set of Events where the _id matches the first query argument, or the 77b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * originalId matches the second argument. Returns the _id field from the set of 78b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Instances whose event_id field matches one of those events. 79b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 80f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final String SQL_WHERE_ID_FROM_INSTANCES_NOT_SYNCED = 812ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik Instances._ID + " IN " + 822ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik "(SELECT " + Tables.INSTANCES + "." + Instances._ID + " as _id" + 83f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " FROM " + Tables.INSTANCES + 84f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " INNER JOIN " + Tables.EVENTS + 85f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " ON (" + 86f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Tables.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID + 87f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik ")" + 88b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden " WHERE " + Tables.EVENTS + "." + Events._ID + "=? OR " + 89b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden Tables.EVENTS + "." + Events.ORIGINAL_ID + "=?)"; 90f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 91b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /** 92b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Determines the set of Events where the _sync_id matches the first query argument, or the 93b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * originalSyncId matches the second argument. Returns the _id field from the set of 94b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Instances whose event_id field matches one of those events. 95b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 96f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final String SQL_WHERE_ID_FROM_INSTANCES_SYNCED = 972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik Instances._ID + " IN " + 982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik "(SELECT " + Tables.INSTANCES + "." + Instances._ID + " as _id" + 99f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " FROM " + Tables.INSTANCES + 100f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " INNER JOIN " + Tables.EVENTS + 101f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " ON (" + 102f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Tables.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID + 103f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik ")" + 104f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik " WHERE " + Tables.EVENTS + "." + Events._SYNC_ID + "=?" + " OR " + 105c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik Tables.EVENTS + "." + Events.ORIGINAL_SYNC_ID + "=?)"; 106f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 107f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final String[] EXPAND_COLUMNS = new String[] { 108f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events._ID, 109f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events._SYNC_ID, 110f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.STATUS, 111f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.DTSTART, 112f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.DTEND, 113f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.EVENT_TIMEZONE, 114f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.RRULE, 115f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.RDATE, 116f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.EXRULE, 117f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.EXDATE, 118f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.DURATION, 119f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.ALL_DAY, 120c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik Events.ORIGINAL_SYNC_ID, 121f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.ORIGINAL_INSTANCE_TIME, 122f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.CALENDAR_ID, 123f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Events.DELETED 124f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }; 125f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 126f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // To determine if a recurrence exception originally overlapped the 127f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window, we need to assume a maximum duration, since we only know 128f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the original start time. 129f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private static final int MAX_ASSUMED_DURATION = 7 * 24 * 60 * 60 * 1000; 130f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 1314caf8d015918f619a67d321a152f150a01022717Andy McFadden public CalendarInstancesHelper(CalendarDatabaseHelper calendarDbHelper, MetaData metaData) { 132f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik mDbHelper = calendarDbHelper; 133f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik mDb = mDbHelper.getWritableDatabase(); 1344caf8d015918f619a67d321a152f150a01022717Andy McFadden mMetaData = metaData; 135f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik mCalendarCache = new CalendarCache(mDbHelper); 136f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 137f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 138f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 139b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Extract the value from the specifed row and column of the Events table. 140b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * 141b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * @param db The database to access. 142b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * @param rowId The Event's _id. 143b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * @param columnName The name of the column to access. 144b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * @return The value in string form. 145b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 146b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden private static String getEventValue(SQLiteDatabase db, long rowId, String columnName) { 147b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String where = "SELECT " + columnName + " FROM " + Tables.EVENTS + 148b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden " WHERE " + Events._ID + "=?"; 149b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden return DatabaseUtils.stringForQuery(db, where, 150b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden new String[] { String.valueOf(rowId) }); 151b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 152b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 153b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /** 154f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Perform instance expansion on the given entries. 155f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 156f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param begin Window start (ms). 157f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param end Window end (ms). 158f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param localTimezone 159f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param entries The entries to process. 160f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 161f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik protected void performInstanceExpansion(long begin, long end, String localTimezone, 162f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Cursor entries) { 163b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // TODO: this only knows how to work with events that have been synced with the server 164f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik RecurrenceProcessor rp = new RecurrenceProcessor(); 165f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 166f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Key into the instance values to hold the original event concatenated 167f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // with calendar id. 168f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik final String ORIGINAL_EVENT_AND_CALENDAR = "ORIGINAL_EVENT_AND_CALENDAR"; 169f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 170f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int statusColumn = entries.getColumnIndex(Events.STATUS); 171f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int dtstartColumn = entries.getColumnIndex(Events.DTSTART); 172f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int dtendColumn = entries.getColumnIndex(Events.DTEND); 173f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE); 174f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int durationColumn = entries.getColumnIndex(Events.DURATION); 175f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int rruleColumn = entries.getColumnIndex(Events.RRULE); 176f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int rdateColumn = entries.getColumnIndex(Events.RDATE); 177f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int exruleColumn = entries.getColumnIndex(Events.EXRULE); 178f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int exdateColumn = entries.getColumnIndex(Events.EXDATE); 179f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int allDayColumn = entries.getColumnIndex(Events.ALL_DAY); 180f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int idColumn = entries.getColumnIndex(Events._ID); 181f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID); 182c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_SYNC_ID); 183f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME); 184f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int calendarIdColumn = entries.getColumnIndex(Events.CALENDAR_ID); 185f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int deletedColumn = entries.getColumnIndex(Events.DELETED); 186f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 187f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik ContentValues initialValues; 188f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.EventInstancesMap instancesMap = 189f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik new CalendarInstancesHelper.EventInstancesMap(); 190f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 191f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Duration duration = new Duration(); 192f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Time eventTime = new Time(); 193f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 194f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Invariant: entries contains all events that affect the current 195f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window. It consists of: 196f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // a) Individual events that fall in the window. These will be 197f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // displayed. 198f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // b) Recurrences that included the window. These will be displayed 199f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // if not canceled. 200f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // c) Recurrence exceptions that fall in the window. These will be 201f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // displayed if not cancellations. 202f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // d) Recurrence exceptions that modify an instance inside the 203f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window (subject to 1 week assumption above), but are outside 204f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the window. These will not be displayed. Cases c and d are 205b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // distinguished by the start / end time. 206f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 207f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik while (entries.moveToNext()) { 208f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik try { 209f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues = null; 210f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 211f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean allDay = entries.getInt(allDayColumn) != 0; 212f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 213f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String eventTimezone = entries.getString(eventTimezoneColumn); 214f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDay || TextUtils.isEmpty(eventTimezone)) { 215f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // in the events table, allDay events start at midnight. 216f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // this forces them to stay at midnight for all day events 217f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // TODO: check that this actually does the right thing. 218f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTimezone = Time.TIMEZONE_UTC; 219f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 220f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 221f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long dtstartMillis = entries.getLong(dtstartColumn); 222f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Long eventId = Long.valueOf(entries.getLong(idColumn)); 223f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 224f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String durationStr = entries.getString(durationColumn); 225f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (durationStr != null) { 226f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik try { 227f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.parse(durationStr); 228f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 229f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik catch (DateException e) { 230f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 231f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.w(CalendarProvider2.TAG, "error parsing duration for event " 232f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + eventId + "'" + durationStr + "'", e); 233f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 234f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.sign = 1; 235f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.weeks = 0; 236f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.days = 0; 237f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.hours = 0; 238f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.minutes = 0; 239f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.seconds = 0; 240f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik durationStr = "+P0S"; 241f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 242f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 243f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 244f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String syncId = entries.getString(syncIdColumn); 245f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String originalEvent = entries.getString(originalEventColumn); 246f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 247f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long originalInstanceTimeMillis = -1; 248f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (!entries.isNull(originalInstanceTimeColumn)) { 249f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn); 250f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 251f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int status = entries.getInt(statusColumn); 252f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean deleted = (entries.getInt(deletedColumn) != 0); 253f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 254f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String rruleStr = entries.getString(rruleColumn); 255f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String rdateStr = entries.getString(rdateColumn); 256f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String exruleStr = entries.getString(exruleColumn); 257f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String exdateStr = entries.getString(exdateColumn); 258f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long calendarId = entries.getLong(calendarIdColumn); 259f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // key into instancesMap 260f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String syncIdKey = CalendarInstancesHelper.getSyncIdKey(syncId, calendarId); 261f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 262f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik RecurrenceSet recur = null; 263f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik try { 264f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr); 265f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } catch (EventRecurrence.InvalidFormatException e) { 266f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 267f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.w(CalendarProvider2.TAG, "Could not parse RRULE recurrence string: " 268f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + rruleStr, e); 269f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 270f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 271f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 272f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 273f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (null != recur && recur.hasRecurrence()) { 274f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the event is repeating 275f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 276f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (status == Events.STATUS_CANCELED) { 277f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // should not happen! 278f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 279f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.e(CalendarProvider2.TAG, "Found canceled recurring event in " 280f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + "Events table. Ignoring."); 281f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 282f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 283f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 284f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (deleted) { 285f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.DEBUG)) { 286f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.d(CalendarProvider2.TAG, "Found deleted recurring event in " 287f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + "Events table. Ignoring."); 288f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 289f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 290f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 291f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 292f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // need to parse the event into a local calendar. 293f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.timezone = eventTimezone; 294f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.set(dtstartMillis); 295f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.allDay = allDay; 296f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 297f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (durationStr == null) { 298f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // should not happen. 299f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 300f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.e(CalendarProvider2.TAG, "Repeating event has no duration -- " 301f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + "should not happen."); 302f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 303f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDay) { 304f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // set to one day. 305f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.sign = 1; 306f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.weeks = 0; 307f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.days = 1; 308f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.hours = 0; 309f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.minutes = 0; 310f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.seconds = 0; 311f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik durationStr = "+P1D"; 312f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 313f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // compute the duration from dtend, if we can. 314f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // otherwise, use 0s. 315f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.sign = 1; 316f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.weeks = 0; 317f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.days = 0; 318f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.hours = 0; 319f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.minutes = 0; 320f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (!entries.isNull(dtendColumn)) { 321f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long dtendMillis = entries.getLong(dtendColumn); 322f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000); 323f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik durationStr = "+P" + duration.seconds + "S"; 324f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 325f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik duration.seconds = 0; 326f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik durationStr = "+P0S"; 327f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 328f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 329f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 330f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 331f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long[] dates; 332f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik dates = rp.expand(eventTime, recur, begin, end); 333f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 334f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Initialize the "eventTime" timezone outside the loop. 335f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // This is used in computeTimezoneDependentFields(). 336f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDay) { 337f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.timezone = Time.TIMEZONE_UTC; 338f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 339f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.timezone = localTimezone; 340f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 341f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 342f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long durationMillis = duration.getMillis(); 343f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (long date : dates) { 344f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues = new ContentValues(); 345f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.EVENT_ID, eventId); 346f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 347f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.BEGIN, date); 348f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long dtendMillis = date + durationMillis; 349f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.END, dtendMillis); 350f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 351f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.computeTimezoneDependentFields(date, dtendMillis, 352f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime, initialValues); 353f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instancesMap.add(syncIdKey, initialValues); 354f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 355f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 356f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the event is not repeating 357f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues = new ContentValues(); 358f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 359f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // if this event has an "original" field, then record 360f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // that we need to cancel the original event (we can't 361f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // do that here because the order of this loop isn't 362f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // defined) 363f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (originalEvent != null && originalInstanceTimeMillis != -1) { 364f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // The ORIGINAL_EVENT_AND_CALENDAR holds the 365f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // calendar id concatenated with the ORIGINAL_EVENT to form 366f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // a unique key, matching the keys for instancesMap. 367f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(ORIGINAL_EVENT_AND_CALENDAR, 368f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.getSyncIdKey(originalEvent, calendarId)); 369f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Events.ORIGINAL_INSTANCE_TIME, 370f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik originalInstanceTimeMillis); 371f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Events.STATUS, status); 372f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 373f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 374f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long dtendMillis = dtstartMillis; 375f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (durationStr == null) { 376f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (!entries.isNull(dtendColumn)) { 377f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik dtendMillis = entries.getLong(dtendColumn); 378f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 379f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 380f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik dtendMillis = duration.addTo(dtstartMillis); 381f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 382f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 383f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // this non-recurring event might be a recurrence exception that doesn't 384f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // actually fall within our expansion window, but instead was selected 385f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // so we can correctly cancel expanded recurrence instances below. do not 386f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // add events to the instances map if they don't actually fall within our 387f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // expansion window. 388f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if ((dtendMillis < begin) || (dtstartMillis > end)) { 389f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (originalEvent != null && originalInstanceTimeMillis != -1) { 390f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Events.STATUS, Events.STATUS_CANCELED); 391f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 392f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 393f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.w(CalendarProvider2.TAG, "Unexpected event outside window: " 394f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik + syncId); 395f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 396f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 397f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 398f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 399f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 400f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.EVENT_ID, eventId); 401f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 402f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.BEGIN, dtstartMillis); 403f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Instances.END, dtendMillis); 404f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 405f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // we temporarily store the DELETED status (will be cleaned later) 406f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik initialValues.put(Events.DELETED, deleted); 407f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 408f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDay) { 409f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.timezone = Time.TIMEZONE_UTC; 410f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 411f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik eventTime.timezone = localTimezone; 412f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 413f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.computeTimezoneDependentFields(dtstartMillis, 414f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik dtendMillis, eventTime, initialValues); 415f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 416f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instancesMap.add(syncIdKey, initialValues); 417f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 418f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } catch (DateException e) { 419f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 420f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.w(CalendarProvider2.TAG, "RecurrenceProcessor error ", e); 421f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 422f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } catch (TimeFormatException e) { 423f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(CalendarProvider2.TAG, Log.ERROR)) { 424f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.w(CalendarProvider2.TAG, "RecurrenceProcessor error ", e); 425f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 426f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 427f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 428f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 429f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Invariant: instancesMap contains all instances that affect the 430f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window, indexed by original sync id concatenated with calendar id. 431f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // It consists of: 432f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // a) Individual events that fall in the window. They have: 433f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // EVENT_ID, BEGIN, END 434f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // b) Instances of recurrences that fall in the window. They may 435f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // be subject to exceptions. They have: 436f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // EVENT_ID, BEGIN, END 437f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // c) Exceptions that fall in the window. They have: 438f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS (since they can 439f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // be a modification or cancellation), EVENT_ID, BEGIN, END 440f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // d) Recurrence exceptions that modify an instance inside the 441f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window but fall outside the window. They have: 442f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS = 443f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // STATUS_CANCELED, EVENT_ID, BEGIN, END 444f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 445f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // First, delete the original instances corresponding to recurrence 446f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // exceptions. We do this by iterating over the list and for each 447f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // recurrence exception, we search the list for an instance with a 448f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // matching "original instance time". If we find such an instance, 449f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // we remove it from the list. If we don't find such an instance 450f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // then we cancel the recurrence exception. 451f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Set<String> keys = instancesMap.keySet(); 452f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (String syncIdKey : keys) { 453f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.InstancesList list = instancesMap.get(syncIdKey); 454f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (ContentValues values : list) { 455f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 456f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // If this instance is not a recurrence exception, then 457f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // skip it. 458f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (!values.containsKey(ORIGINAL_EVENT_AND_CALENDAR)) { 459f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 460f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 461f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 462f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String originalEventPlusCalendar = values.getAsString(ORIGINAL_EVENT_AND_CALENDAR); 463f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 464f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.InstancesList originalList = instancesMap 465f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik .get(originalEventPlusCalendar); 466f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (originalList == null) { 467f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // The original recurrence is not present, so don't try canceling it. 468f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 469f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 470f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 471f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Search the original event for a matching original 472f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // instance time. If there is a matching one, then remove 473f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the original one. We do this both for exceptions that 474f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // change the original instance as well as for exceptions 475f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // that delete the original instance. 476f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (int num = originalList.size() - 1; num >= 0; num--) { 477f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik ContentValues originalValues = originalList.get(num); 478f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik long beginTime = originalValues.getAsLong(Instances.BEGIN); 479f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (beginTime == originalTime) { 480f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // We found the original instance, so remove it. 481f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik originalList.remove(num); 482f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 483f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 484f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 485f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 486f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 487f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Invariant: instancesMap contains filtered instances. 488f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // It consists of: 489f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // a) Individual events that fall in the window. 490f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // b) Instances of recurrences that fall in the window and have not 491f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // been subject to exceptions. 492f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // c) Exceptions that fall in the window. They will have 493f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // STATUS_CANCELED if they are cancellations. 494f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // d) Recurrence exceptions that modify an instance inside the 495f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // window but fall outside the window. These are STATUS_CANCELED. 496f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 497f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Now do the inserts. Since the db lock is held when this method is executed, 498f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // this will be done in a transaction. 499f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db 500f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // while the calendar app is trying to query the db (expanding instances)), we will 501f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // not be "polite" and yield the lock until we're done. This will favor local query 502f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // operations over sync/write operations. 503f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (String syncIdKey : keys) { 504f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.InstancesList list = instancesMap.get(syncIdKey); 505f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik for (ContentValues values : list) { 506f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 507f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // If this instance was cancelled or deleted then don't create a new 508f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // instance. 509f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Integer status = values.getAsInteger(Events.STATUS); 510f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean deleted = values.containsKey(Events.DELETED) ? 511f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.getAsBoolean(Events.DELETED) : false; 512f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if ((status != null && status == Events.STATUS_CANCELED) || deleted) { 513f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik continue; 514f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 515f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 516f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // We remove this useless key (not valid in the context of Instances table) 517f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.remove(Events.DELETED); 518f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 519f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Remove these fields before inserting a new instance 520f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.remove(ORIGINAL_EVENT_AND_CALENDAR); 521f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.remove(Events.ORIGINAL_INSTANCE_TIME); 522f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.remove(Events.STATUS); 523f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 524f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik mDbHelper.instancesReplace(values); 525f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 526f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 527f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 528f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 529f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 530f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Make instances for the given range. 531f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 532f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik protected void expandInstanceRangeLocked(long begin, long end, String localTimezone) { 533f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 534f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (CalendarProvider2.PROFILE) { 535f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Debug.startMethodTracing("expandInstanceRangeLocked"); 536f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 537f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 538f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(TAG, Log.VERBOSE)) { 539f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.v(TAG, "Expanding events between " + begin + " and " + end); 540f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 541f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 542f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Cursor entries = getEntries(begin, end); 543f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik try { 544f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik performInstanceExpansion(begin, end, localTimezone, entries); 545f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } finally { 546f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (entries != null) { 547f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik entries.close(); 548f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 549f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 550f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (CalendarProvider2.PROFILE) { 551f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Debug.stopMethodTracing(); 552f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 553f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 554f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 555f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 556f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Get all entries affecting the given window. 557f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 558f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param begin Window start (ms). 559f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param end Window end (ms). 560f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @return Cursor for the entries; caller must close it. 561f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 562f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private Cursor getEntries(long begin, long end) { 563f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 564f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 565f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.setProjectionMap(CalendarProvider2.sEventsProjectionMap); 566f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 567f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String beginString = String.valueOf(begin); 568f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String endString = String.valueOf(end); 569f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 570f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // grab recurrence exceptions that fall outside our expansion window but 571f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // modify 572f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // recurrences that do fall within our window. we won't insert these 573f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // into the output 574f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // set of instances, but instead will just add them to our cancellations 575f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // list, so we 576f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // can cancel the correct recurrence expansion instances. 577f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // we don't have originalInstanceDuration or end time. for now, assume 578f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the original 579f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // instance lasts no longer than 1 week. 580f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // also filter with syncable state (we dont want the entries from a non 581f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // syncable account) 5829ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert // also filter with last_synced=0 so we don't expand events that were 5839ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert // dup'ed for partial updates. 584f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // TODO: compute the originalInstanceEndTime or get this from the 585f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // server. 586f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.appendWhere(SQL_WHERE_GET_EVENTS_ENTRIES); 587f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String selectionArgs[] = new String[] { 5889ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert endString, 5899ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert beginString, 5909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert endString, 5919ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert String.valueOf(begin - MAX_ASSUMED_DURATION), 5929ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert "0", // Calendars.SYNC_EVENTS 5939ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert "0", // Events.LAST_SYNCED 594f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }; 595f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Cursor c = qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs, 596f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik null /* groupBy */, null /* having */, null /* sortOrder */); 597f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(TAG, Log.VERBOSE)) { 598f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.v(TAG, "Instance expansion: got " + c.getCount() + " entries"); 599f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 600f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return c; 601f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 602f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 603f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 604f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Updates the instances table when an event is added or updated. 605f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 606f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param values The new values of the event. 607f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param rowId The database row id of the event. 608f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param newEvent true if the event is new. 609f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param db The database 610f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 611f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik public void updateInstancesLocked(ContentValues values, long rowId, boolean newEvent, 612f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik SQLiteDatabase db) { 613b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /* 614b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * This may be a recurring event (has an RRULE or RDATE), an exception to a recurring 615b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * event (has ORIGINAL_ID or ORIGINAL_SYNC_ID), or a regular event. Recurring events 616b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * and exceptions require additional handling. 617b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * 618b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * If this is not a new event, it may already have entries in Instances, so we want 619b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * to delete those before we do any additional work. 620b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 621f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 622f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // If there are no expanded Instances, then return. 623f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik MetaData.Fields fields = mMetaData.getFieldsLocked(); 624f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (fields.maxInstance == 0) { 625f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return; 626f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 627f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 628f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Long dtstartMillis = values.getAsLong(Events.DTSTART); 629f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (dtstartMillis == null) { 630f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (newEvent) { 631f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // must be present for a new event. 632f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik throw new RuntimeException("DTSTART missing."); 633f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 634f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(TAG, Log.VERBOSE)) { 635f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.v(TAG, "Missing DTSTART. No need to update instance."); 636f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 637f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return; 638f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 639f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 640f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (!newEvent) { 641f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Want to do this for regular event, recurrence, or exception. 642f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // For recurrence or exception, more deletion may happen below if we 643f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // do an instance expansion. This deletion will suffice if the 644f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // exception 645f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // is moved outside the window, for instance. 646f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik db.delete(Tables.INSTANCES, Instances.EVENT_ID + "=?", new String[] { 647f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String.valueOf(rowId) 648f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }); 649f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 650f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 651f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String rrule = values.getAsString(Events.RRULE); 652f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String rdate = values.getAsString(Events.RDATE); 653b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String originalId = values.getAsString(Events.ORIGINAL_ID); 654b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID); 655b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (CalendarProvider2.isRecurrenceEvent(rrule, rdate, originalId, originalSyncId)) { 656b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden Long lastDateMillis = values.getAsLong(Events.LAST_DATE); 657b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 658b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 659f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // The recurrence or exception needs to be (re-)expanded if: 660f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // a) Exception or recurrence that falls inside window 661f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean insideWindow = dtstartMillis <= fields.maxInstance 662f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik && (lastDateMillis == null || lastDateMillis >= fields.minInstance); 663f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // b) Exception that affects instance inside window 664f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // These conditions match the query in getEntries 665f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // See getEntries comment for explanation of subtracting 1 week. 666f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean affectsWindow = originalInstanceTime != null 667f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik && originalInstanceTime <= fields.maxInstance 668f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik && originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION; 669d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden if (CalendarProvider2.DEBUG_INSTANCES) { 670d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden Log.d(TAG + "-i", "Recurrence: inside=" + insideWindow + 671d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden ", affects=" + affectsWindow); 672d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden } 673f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (insideWindow || affectsWindow) { 674f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik updateRecurrenceInstancesLocked(values, rowId, db); 675f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 676f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // TODO: an exception creation or update could be optimized by 677f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // updating just the affected instances, instead of regenerating 678f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // the recurrence. 679f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return; 680f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 681f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 682f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Long dtendMillis = values.getAsLong(Events.DTEND); 683f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (dtendMillis == null) { 684f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik dtendMillis = dtstartMillis; 685f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 686f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 687f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // if the event is in the expanded range, insert 688f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // into the instances table. 689f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // TODO: deal with durations. currently, durations are only used in 690f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // recurrences. 691f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 692f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) { 693f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik ContentValues instanceValues = new ContentValues(); 694f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instanceValues.put(Instances.EVENT_ID, rowId); 695f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instanceValues.put(Instances.BEGIN, dtstartMillis); 696f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instanceValues.put(Instances.END, dtendMillis); 697f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 698f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik boolean allDay = false; 699f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Integer allDayInteger = values.getAsInteger(Events.ALL_DAY); 700f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDayInteger != null) { 701f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik allDay = allDayInteger != 0; 702f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 703f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 704f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Update the timezone-dependent fields. 705f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Time local = new Time(); 706f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (allDay) { 707f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik local.timezone = Time.TIMEZONE_UTC; 708f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 709f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik local.timezone = fields.timezone; 710f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 711f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 712f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik CalendarInstancesHelper.computeTimezoneDependentFields(dtstartMillis, dtendMillis, 713f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik local, instanceValues); 714f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik mDbHelper.instancesInsert(instanceValues); 715f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 716f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 717f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 718f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 719f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Do incremental Instances update of a recurrence or recurrence exception. 720f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * This method does performInstanceExpansion on just the modified 721f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * recurrence, to avoid the overhead of recomputing the entire instance 722f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * table. 723f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 724f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param values The new values of the event. 725f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param rowId The database row id of the event. 726f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param db The database 727f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 728f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private void updateRecurrenceInstancesLocked(ContentValues values, long rowId, 729f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik SQLiteDatabase db) { 730b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /* 731b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * There are two categories of event that "rowId" may refer to: 732b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * (1) Recurrence event. 733b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * (2) Exception to recurrence event. Has non-empty originalId (if it originated 734b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * locally), originalSyncId (if it originated from the server), or both (if 735b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * it's fully synchronized). 736b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * 737b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Exceptions may arrive from the server before the recurrence event, which means: 738b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * - We could find an originalSyncId but a lookup on originalSyncId could fail (in 739b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * which case we can just ignore the exception for now). 740b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * - There may be a brief period between the time we receive a recurrence and the 741b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * time we set originalId in related exceptions where originalSyncId is the only 742b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * way to find exceptions for a recurrence. Thus, an empty originalId field may 743b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * not be used to decide if an event is an exception. 744b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 745b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 746f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik MetaData.Fields fields = mMetaData.getFieldsLocked(); 747f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String instancesTimezone = mCalendarCache.readTimezoneInstances(); 748b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 749b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // Get the originalSyncId. If it's not in "values", check the database. 750b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID); 751b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (originalSyncId == null) { 752b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden originalSyncId = getEventValue(db, rowId, Events.ORIGINAL_SYNC_ID); 753b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 754b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 755f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String recurrenceSyncId; 756b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (originalSyncId != null) { 757b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // This event is an exception; set recurrenceSyncId to the original. 758b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceSyncId = originalSyncId; 759f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 760b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // This could be a recurrence or an exception. If it has been synced with the 761b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // server we can get the _sync_id and know for certain that it's a recurrence. 762b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // If not, we'll deal with it below. 763b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceSyncId = values.getAsString(Events._SYNC_ID); 764b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (recurrenceSyncId == null) { 765b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // Not in "values", check the database. 766b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceSyncId = getEventValue(db, rowId, Events._SYNC_ID); 767b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 768f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 769f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 770b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // Clear out old instances 771b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden int delCount; 772f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (recurrenceSyncId == null) { 773b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // We're creating or updating a recurrence or exception that hasn't been to the 774b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // server. If this is a recurrence event, the event ID is simply the rowId. If 775b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // it's an exception, we will find the value in the originalId field. 776b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String originalId = values.getAsString(Events.ORIGINAL_ID); 777b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (originalId == null) { 778b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // Not in "values", check the database. 779b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden originalId = getEventValue(db, rowId, Events.ORIGINAL_ID); 780b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 781b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden String recurrenceId; 782b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden if (originalId != null) { 783b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // This event is an exception; set recurrenceId to the original. 784b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceId = originalId; 785b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } else { 786b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // This event is a recurrence, so we just use the ID that was passed in. 787b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceId = String.valueOf(rowId); 788b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 789b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 790b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // Delete Instances entries for this Event (_id == recurrenceId) and for exceptions 791b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // to this Event (originalId == recurrenceId). 792f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String where = SQL_WHERE_ID_FROM_INSTANCES_NOT_SYNCED; 793b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden delCount = db.delete(Tables.INSTANCES, where, new String[] { 794b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden recurrenceId, recurrenceId 795f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }); 796f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 797b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // We're creating or updating a recurrence or exception that has been synced with 798b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // the server. Delete Instances entries for this Event (_sync_id == recurrenceSyncId) 799b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // and for exceptions to this Event (originalSyncId == recurrenceSyncId). 800f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String where = SQL_WHERE_ID_FROM_INSTANCES_SYNCED; 801b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden delCount = db.delete(Tables.INSTANCES, where, new String[] { 802f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik recurrenceSyncId, recurrenceSyncId 803f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }); 804f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 805f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 806b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden //Log.d(TAG, "Recurrence: deleted " + delCount + " instances"); 807b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden //dumpInstancesTable(db); 808b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden 809f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Now do instance expansion 810b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden // TODO: passing "rowId" is wrong if this is an exception - need originalId then 811f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId); 812f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik try { 813f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik performInstanceExpansion(fields.minInstance, fields.maxInstance, 814f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik instancesTimezone, entries); 815f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } finally { 816f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (entries != null) { 817f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik entries.close(); 818f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 819f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 820f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 821f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 822f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 823f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Determines the recurrence entries associated with a particular 824f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * recurrence. This set is the base recurrence and any exception. Normally 825f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * the entries are indicated by the sync id of the base recurrence (which is 826b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * the originalSyncId in the exceptions). However, a complication is that a 827f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * recurrence may not yet have a sync id. In that case, the recurrence is 828f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * specified by the rowId. 829f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 830f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param recurrenceSyncId The sync id of the base recurrence, or null. 831f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param rowId The row id of the base recurrence. 832f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @return the relevant entries. 833f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 834f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) { 835f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 836f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 837f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 838f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.setProjectionMap(CalendarProvider2.sEventsProjectionMap); 839f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String selectionArgs[]; 840f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (recurrenceSyncId == null) { 841f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String where = CalendarProvider2.SQL_WHERE_ID; 842f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.appendWhere(where); 843f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik selectionArgs = new String[] { 844f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik String.valueOf(rowId) 845f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }; 846f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } else { 8479ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert // don't expand events that were dup'ed for partial updates 8489ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert String where = "(" + Events._SYNC_ID + "=? OR " + Events.ORIGINAL_SYNC_ID + "=?) AND " 8499ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert + Events.LAST_SYNCED + " = ?"; 850f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik qb.appendWhere(where); 851f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik selectionArgs = new String[] { 8529ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert recurrenceSyncId, 8539ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert recurrenceSyncId, 8549ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert "0", // Events.LAST_SYNCED 855f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik }; 856f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 857f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (Log.isLoggable(TAG, Log.VERBOSE)) { 858f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Log.v(TAG, "Retrieving events to expand: " + qb.toString()); 859f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 860f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 861f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs, 862f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik null /* groupBy */, null /* having */, null /* sortOrder */); 863f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 864f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 865f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 866f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Generates a unique key from the syncId and calendarId. The purpose of 867f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * this is to prevent collisions if two different calendars use the same 868f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * sync id. This can happen if a Google calendar is accessed by two 869f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * different accounts, or with Exchange, where ids are not unique between 870f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * calendars. 871f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 872f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param syncId Id for the event 873f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param calendarId Id for the calendar 874f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @return key 875f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 876f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik static String getSyncIdKey(String syncId, long calendarId) { 877f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik return calendarId + ":" + syncId; 878f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 879f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 880f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik /** 881f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * Computes the timezone-dependent fields of an instance of an event and 882f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * updates the "values" map to contain those fields. 883f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * 884f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param begin the start time of the instance (in UTC milliseconds) 885f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param end the end time of the instance (in UTC milliseconds) 886f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param local a Time object with the timezone set to the local timezone 887f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik * @param values a map that will contain the timezone-dependent fields 888f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik */ 889f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik static void computeTimezoneDependentFields(long begin, long end, 890f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik Time local, ContentValues values) { 891f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik local.set(begin); 892f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int startDay = Time.getJulianDay(begin, local.gmtoff); 893f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int startMinute = local.hour * 60 + local.minute; 894f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 895f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik local.set(end); 896f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int endDay = Time.getJulianDay(end, local.gmtoff); 897f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik int endMinute = local.hour * 60 + local.minute; 898f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 899f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Special case for midnight, which has endMinute == 0. Change 900f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // that to +24 hours on the previous day to make everything simpler. 901f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // Exception: if start and end minute are both 0 on the same day, 902f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik // then leave endMinute alone. 903f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik if (endMinute == 0 && endDay > startDay) { 904f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik endMinute = 24 * 60; 905f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik endDay -= 1; 906f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 907f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 908f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.put(Instances.START_DAY, startDay); 909f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.put(Instances.END_DAY, endDay); 910f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.put(Instances.START_MINUTE, startMinute); 911f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik values.put(Instances.END_MINUTE, endMinute); 912f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik } 913f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik 914b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden /** 915b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden * Dumps the contents of the Instances table to the log file. 916b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden */ 917b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden private static void dumpInstancesTable(SQLiteDatabase db) { 918b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden Cursor cursor = db.query(Tables.INSTANCES, null, null, null, null, null, null); 919b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden DatabaseUtils.dumpCursor(cursor); 9200332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan if (cursor != null) { 9210332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan cursor.close(); 9220332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan } 923b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden } 924f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik} 925