CalendarProvider2.java revision 18f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbc
19f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/* 29f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** 39f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Copyright 2006, The Android Open Source Project 49f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** 59f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Licensed under the Apache License, Version 2.0 (the "License"); 69f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** you may not use this file except in compliance with the License. 79f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** You may obtain a copy of the License at 89f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** 99f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** http://www.apache.org/licenses/LICENSE-2.0 109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** 119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Unless required by applicable law or agreed to in writing, software 129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** distributed under the License is distributed on an "AS IS" BASIS, 139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** See the License for the specific language governing permissions and 149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** limitations under the License. 169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff*/ 179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpackage com.android.providers.calendar; 199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 20370f91c0cfe5a5fecaba6120e703f4d2271d2277Erikimport com.google.common.annotations.VisibleForTesting; 21370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik 229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account; 239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager; 249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener; 259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.AlarmManager; 269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.PendingIntent; 279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver; 289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver; 299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris; 309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues; 319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context; 329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent; 339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter; 349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher; 359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor; 369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils; 379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException; 389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase; 399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder; 409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri; 419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Debug; 42a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler; 43a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message; 449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process; 45f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglioimport android.pim.EventRecurrence; 469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet; 479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns; 489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar; 499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Attendees; 509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.CalendarAlerts; 519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Calendars; 529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Events; 539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Instances; 549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Reminders; 559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils; 561edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils; 57192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time; 589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Config; 599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log; 609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException; 61ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils; 629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList; 64ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays; 659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap; 669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet; 67dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List; 689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Set; 699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone; 70dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher; 7181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern; 729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/** 749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications 759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * is defined in {@link android.provider.Calendar}. 769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener { 789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final String TAG = "CalendarProvider2"; 809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final boolean PROFILE = false; 829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true; 838f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 848f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff private static final String INVALID_CALENDARALERTS_SELECTOR = 858f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff "_id IN (SELECT ca._id FROM CalendarAlerts AS ca" 868f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " LEFT OUTER JOIN Instances USING (event_id, begin, end)" 878f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " LEFT OUTER JOIN Reminders AS r ON" 888f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " (ca.event_id=r.event_id AND ca.minutes=r.minutes)" 898f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " WHERE Instances.begin ISNULL OR ca.alarmTime<?" 908f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " OR (r.minutes ISNULL AND ca.minutes<>0))"; 918f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff private static final String[] ID_ONLY_PROJECTION = 931ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff new String[] {Events._ID}; 949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final String[] EVENTS_PROJECTION = new String[] { 969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events._SYNC_ID, 979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.RRULE, 989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.RDATE, 999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.ORIGINAL_EVENT, 1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff }; 1019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int EVENTS_SYNC_ID_INDEX = 0; 1027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final int EVENTS_RRULE_INDEX = 1; 1037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final int EVENTS_RDATE_INDEX = 2; 1047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3; 1057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 1067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final String[] ID_PROJECTION = new String[] { 1077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff Attendees._ID, 1087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff Attendees.EVENT_ID, // Assume these are the same for each table 1097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff }; 1107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final int ID_INDEX = 0; 1117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private static final int EVENT_ID_INDEX = 1; 1129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 114646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * Projection to query for correcting times in allDay events. 115646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik */ 116646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final String[] ALLDAY_TIME_PROJECTION = new String[] { 117646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Events._ID, 118646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Events.DTSTART, 119646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Events.DTEND, 120646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Events.DURATION 121646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik }; 122646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final int ALLDAY_ID_INDEX = 0; 123646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final int ALLDAY_DTSTART_INDEX = 1; 124646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final int ALLDAY_DTEND_INDEX = 2; 125646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final int ALLDAY_DURATION_INDEX = 3; 126646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 127646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private static final int DAY_IN_SECONDS = 24 * 60 * 60; 128646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 129646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik /** 1309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * The cached copy of the CalendarMetaData database table. 1319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Make this "package private" instead of "private" so that test code 1329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * can access it. 1339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 1349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff MetaData mMetaData; 135ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio CalendarCache mCalendarCache; 1369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private CalendarDatabaseHelper mDbHelper; 1389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final Uri SYNCSTATE_CONTENT_URI = 1409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Uri.parse("content://syncstate/state"); 14183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // 14283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false) 14383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true) 14483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // TODO: use a service to schedule alarms rather than private URI 14583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff /* package */ static final String SCHEDULE_ALARM_PATH = "schedule_alarms"; 14683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff /* package */ static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove"; 14783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff /* package */ static final Uri SCHEDULE_ALARM_URI = 14883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_PATH); 14983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff /* package */ static final Uri SCHEDULE_ALARM_REMOVE_URI = 15083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH); 1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // To determine if a recurrence exception originally overlapped the 1539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // window, we need to assume a maximum duration, since we only know 1549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the original start time. 1559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int MAX_ASSUMED_DURATION = 7*24*60*60*1000; 1569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public static final class TimeRange { 1589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public long begin; 1599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public long end; 1609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public boolean allDay; 1619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public static final class InstancesRange { 1649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public long begin; 1659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public long end; 1669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public InstancesRange(long begin, long end) { 1689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff this.begin = begin; 1699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff this.end = end; 1709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public static final class InstancesList 1749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff extends ArrayList<ContentValues> { 1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public static final class EventInstancesMap 1789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff extends HashMap<String, InstancesList> { 1791030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff public void add(String syncIdKey, ContentValues values) { 1801030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff InstancesList instances = get(syncIdKey); 1819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (instances == null) { 1829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff instances = new InstancesList(); 1831030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff put(syncIdKey, instances); 1849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff instances.add(values); 1869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // A thread that runs in the background and schedules the next 1909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // calendar event alarm. 1919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private class AlarmScheduler extends Thread { 1929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean mRemoveAlarms; 1939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public AlarmScheduler(boolean removeAlarms) { 1959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mRemoveAlarms = removeAlarms; 1969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 198192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank @Override 1999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public void run() { 2009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 2019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 2029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff runScheduleNextAlarm(mRemoveAlarms); 2039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (SQLException e) { 2049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "runScheduleNextAlarm() failed", e); 2059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 2069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 2079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 2089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 2109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * We search backward in time for event reminders that we may have missed 2119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * and schedule them if the event has not yet expired. The amount in 2129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * the past to search backwards is controlled by this constant. It 2139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * should be at least a few minutes to allow for an event that was 2149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * recently created on the web to make its way to the phone. Two hours 2159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * might seem like overkill, but it is useful in the case where the user 2169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * just crossed into a new timezone and might have just missed an alarm. 2179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 2181edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS; 2199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 2219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Alarms older than this threshold will be deleted from the CalendarAlerts 2229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * table. This should be at least a day because if the timezone is 2239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * wrong and the user corrects it we might delete good alarms that 2249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * appear to be old because the device time was incorrectly in the future. 2259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This threshold must also be larger than SCHEDULE_ALARM_SLACK. We add 2269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * the SCHEDULE_ALARM_SLACK to ensure this. 2279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 2289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * To make it easier to find and debug problems with missed reminders, 2299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * set this to something greater than a day. 2309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 2319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final long CLEAR_OLD_ALARM_THRESHOLD = 2321edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff 7 * DateUtils.DAY_IN_MILLIS + SCHEDULE_ALARM_SLACK; 2339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // A lock for synchronizing access to fields that are shared 2359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // with the AlarmScheduler thread. 2369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private Object mAlarmLock = new Object(); 2379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Make sure we load at least two months worth of data. 2399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Client apps can load more data in a background thread. 2409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final long MINIMUM_EXPANSION_SPAN = 2419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2L * 31 * 24 * 60 * 60 * 1000; 2429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID }; 2449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDARS_INDEX_ID = 0; 2459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Allocate the string constant once here instead of on the heap 2479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final String CALENDAR_ID_SELECTION = "calendar_id=?"; 2489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final String INSTANCE_QUERY_TABLES = 25081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " + 25181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang CalendarDatabaseHelper.Views.EVENTS + " AS " + 25281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang CalendarDatabaseHelper.Tables.EVENTS + 25381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "." 25481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang + Calendar.Instances.EVENT_ID + "=" + 25581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang CalendarDatabaseHelper.Tables.EVENTS + "." 25681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang + Calendar.Events._ID + ")"; 25781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 25818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" + 25918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " + 26018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Views.EVENTS + " AS " + 26118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Tables.EVENTS + 26218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "." 26318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang + Calendar.Instances.EVENT_ID + "=" + 26418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Tables.EVENTS + "." 26518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang + Calendar.Events._ID + ")" + ") LEFT OUTER JOIN " + 26618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Tables.ATTENDEES + 26718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "." 26818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang + Calendar.Attendees.EVENT_ID + "=" + 26918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang CalendarDatabaseHelper.Tables.EVENTS + "." 27018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang + Calendar.Events._ID + ")"; 27118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang 27281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final String BETWEEN_DAY_WHERE = 27381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Instances.START_DAY + "<=? AND " + 27481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Instances.END_DAY + ">=?"; 27581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 27681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final String BETWEEN_WHERE = 27781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Instances.BEGIN + "<=? AND " + 27881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Instances.END + ">=?"; 2799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES_INDEX_START_DAY = 0; 2819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES_INDEX_END_DAY = 1; 2829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES_INDEX_START_MINUTE = 2; 2839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES_INDEX_END_MINUTE = 3; 2849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES_INDEX_ALL_DAY = 4; 2859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang /** 28781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * A regex for describing how we split search queries into tokens. 288dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * Keeps quoted phrases as one token. 289dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * 290dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * "one \"two three\"" ==> ["one" "two three"] 291dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang */ 292dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang private static final Pattern SEARCH_TOKEN_PATTERN = 293dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words 294dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang + "\"([^\"]*)\""); // second part matches quoted phrases 295dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang /** 296dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * A special character that was use to escape potentially problematic 297dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * characters in search queries. 298dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * 299dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * Note: do not use backslash for this, as it interferes with the regex 300dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * escaping mechanism. 30181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang */ 302dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang private static final String SEARCH_ESCAPE_CHAR = "#"; 303dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang 304dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang /** 305dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * A regex for matching any characters in an incoming search query that we 306dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape 307dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * character itself. 308dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang */ 309dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang private static final Pattern SEARCH_ESCAPE_PATTERN = 310dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])"); 31181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 31218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang /** 31318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang * Alias used for aggregate concatenation of attendee e-mails when grouping 31418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang * attendees by instance. 31518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang */ 31618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang private static final String ATTENDEES_EMAIL_CONCAT = 31718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang "group_concat(" + Calendar.Attendees.ATTENDEE_EMAIL + ")"; 31818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang 31918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang /** 32018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang * Alias used for aggregate concatenation of attendee names when grouping 32118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang * attendees by instance. 32218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang */ 32318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang private static final String ATTENDEES_NAME_CONCAT = 32418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang "group_concat(" + Calendar.Attendees.ATTENDEE_NAME + ")"; 32518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang 32681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final String[] SEARCH_COLUMNS = new String[] { 32781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Events.TITLE, 32881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Calendar.Events.DESCRIPTION, 32918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang Calendar.Events.EVENT_LOCATION, 33018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang ATTENDEES_EMAIL_CONCAT, 33118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang ATTENDEES_NAME_CONCAT 33281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang }; 33381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 3349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private AlarmManager mAlarmManager; 3359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 336a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang /** 337a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * Arbitrary integer that we assign to the messages that we send to this 338a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * thread's handler, indicating that these are requests to send an update 339a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * notification intent. 340a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang */ 341a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang private static final int UPDATE_BROADCAST_MSG = 1; 342a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang 343a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang /** 344a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * Any requests to send a PROVIDER_CHANGED intent will be collapsed over 345a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * this window, to prevent spamming too many intents at once. 346a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang */ 347a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS = 348dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang DateUtils.SECOND_IN_MILLIS; 349dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang 350dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS = 351dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang 30 * DateUtils.SECOND_IN_MILLIS; 352dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang 353dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang private Context mContext; 354a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang 355a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang private final Handler mBroadcastHandler = new Handler() { 356a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang @Override 357a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang public void handleMessage(Message msg) { 358dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang Context context = CalendarProvider2.this.mContext; 359a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang if (msg.what == UPDATE_BROADCAST_MSG) { 360a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang // Broadcast a provider changed intent 361a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang doSendUpdateNotification(); 362dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // Because the handler does not guarantee message delivery in 363dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // the case that the provider is killed, we need to make sure 364dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // that the provider stays alive long enough to deliver the 365dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // notification. This empty service is sufficient to "wedge" the 366dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // process until we stop it here. 367a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang context.stopService(new Intent(context, EmptyService.class)); 368a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } 369a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } 370a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang }; 3719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 3739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Listens for timezone changes and disk-no-longer-full events 3749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 3759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 3769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 3779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public void onReceive(Context context, Intent intent) { 3789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String action = intent.getAction(); 3799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 3809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "onReceive() " + action); 3819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 3829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { 3839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateTimezoneDependentFields(); 3849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 3859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 3869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Try to clean up if things were screwy due to a full disk 3879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateTimezoneDependentFields(); 3889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 3899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else if (Intent.ACTION_TIME_CHANGED.equals(action)) { 3909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 3919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 3929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 3939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff }; 3949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected void verifyAccounts() { 3969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false); 3979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff onAccountsUpdated(AccountManager.get(getContext()).getAccounts()); 3989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 3999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /* Visible for testing */ 4019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 4029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected CalendarDatabaseHelper getDatabaseHelper(final Context context) { 4039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return CalendarDatabaseHelper.getInstance(context); 4049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 4059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 4079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public boolean onCreate() { 4089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff super.onCreate(); 409ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio try { 410ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio return initialize(); 411ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } catch (RuntimeException e) { 412ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio Log.e(TAG, "Cannot start provider", e); 413ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio return false; 414ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 415ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 4169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 417ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio private boolean initialize() { 418dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang mContext = getContext(); 419ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper(); 420ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio mDb = mDbHelper.getWritableDatabase(); 4219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Register for Intent broadcasts 4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff IntentFilter filter = new IntentFilter(); 4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 4269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 4279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff filter.addAction(Intent.ACTION_TIME_CHANGED); 4289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final Context c = getContext(); 4299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We don't ever unregister this because this thread always wants 4319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // to receive notifications, even in the background. And if this 4329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // thread is killed then the whole process will be killed and the 4339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // memory resources will be reclaimed. 4349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff c.registerReceiver(mIntentReceiver, filter); 4359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mMetaData = new MetaData(mDbHelper); 437ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mCalendarCache = new CalendarCache(mDbHelper); 438ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 439ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio postInitialize(); 4409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return true; 4429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 4439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 444ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio protected void postInitialize() { 445ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio Thread thread = new PostInitializeThread(); 446ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio thread.start(); 447ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 448ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio 449ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio private class PostInitializeThread extends Thread { 450ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio @Override 451ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio public void run() { 452ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 453ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio 454ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio verifyAccounts(); 455ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio 456ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio doUpdateTimezoneDependentFields(); 457ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 458ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 459ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio 4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This creates a background thread to check the timezone and update 4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * the timezone dependent fields in the Instances table if the timezone 4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * has changes. 4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected void updateTimezoneDependentFields() { 4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Thread thread = new TimezoneCheckerThread(); 4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff thread.start(); 4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private class TimezoneCheckerThread extends Thread { 4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public void run() { 4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 474ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio doUpdateTimezoneDependentFields(); 4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 4799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This method runs in a background thread. If the timezone has changed 4809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * then the Instances table will be regenerated. 4819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 4829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void doUpdateTimezoneDependentFields() { 483ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio try { 484ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio if (! isSameTimezoneDatabaseVersion()) { 485ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio doProcessEventRawTimes(null /* default current timezone*/, 486ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio TimeUtils.getTimeZoneDatabaseVersion()); 487ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 488ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio if (isSameTimezone()) { 489ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // Even if the timezone hasn't changed, check for missed alarms. 490ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // This code executes when the CalendarProvider2 is created and 491ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // helps to catch missed alarms when the Calendar process is 492ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // killed (because of low-memory conditions) and then restarted. 493ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio rescheduleMissedAlarms(); 494ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio return; 495ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 496ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio regenerateInstancesTable(); 497ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } catch (SQLException e) { 498ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e); 499ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio try { 500ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // Clear at least the in-memory data (and if possible the 501ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // database fields) to force a re-computation of Instances. 502ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio mMetaData.clearInstanceRange(); 503ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } catch (SQLException e2) { 504ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio Log.e(TAG, "clearInstanceRange() also failed: " + e2); 505ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 5069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 507ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 508ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 509ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio protected void doProcessEventRawTimes(String timezone, String timeZoneDatabaseVersion) { 510ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mDb.beginTransaction(); 511ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 512ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio updateEventsStartEndFromEventRawTimesLocked(timezone); 513ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio updateTimezoneDatabaseVersion(timeZoneDatabaseVersion); 514ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio cleanInstancesTable(); 515ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio regenerateInstancesTable(); 516ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 517ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mDb.setTransactionSuccessful(); 518ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } finally { 519ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mDb.endTransaction(); 520ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 521ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 522ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 523ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private void updateEventsStartEndFromEventRawTimesLocked(String timezone) { 524ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Cursor cursor = mDb.query("EventsRawTimes", 5257cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio new String[] { 5267cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.EventsRawTimesColumns.EVENT_ID, 5277cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.EventsRawTimesColumns.DTSTART_2445, 5287cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.EventsRawTimesColumns.DTEND_2445 5297cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio } /* projection */, 530ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio null /* selection */, 531ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio null /* selection args */, 532ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio null /* group by */, 533ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio null /* having */, 534ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio null /* order by */ 535ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio ); 536ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 537ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio while (cursor.moveToNext()) { 538ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio long eventId = cursor.getLong(0); 539ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String dtStart2445 = cursor.getString(1); 540ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String dtEnd2445 = cursor.getString(2); 541ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio updateEventsStartEndLocked(eventId, 542ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio timezone, 543ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio dtStart2445, 544ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio dtEnd2445); 545ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 546ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } finally { 547ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio cursor.close(); 548ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio cursor = null; 549ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 550ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 551ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 552ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private long get2445ToMillis(String timezone, String dt2445) { 553ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (null == dt2445) { 554ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.v( TAG, "Cannot parse null RFC2445 date"); 555ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return 0; 556ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 557ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Time time = (timezone != null) ? new Time(timezone) : new Time(); 558ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 559ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio time.parse(dt2445); 560ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } catch (TimeFormatException e) { 561ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.v( TAG, "Cannot parse RFC2445 date " + dt2445); 562ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return 0; 563ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 564ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return time.toMillis(true /* ignore DST */); 565ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 566ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 567ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private void updateEventsStartEndLocked(long eventId, 568ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String timezone, String dtStart2445, String dtEnd2445) { 569ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 570ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio ContentValues values = new ContentValues(); 571ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio values.put("dtstart", get2445ToMillis(timezone, dtStart2445)); 572ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio values.put("dtend", get2445ToMillis(timezone, dtEnd2445)); 573ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 574dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff int result = mDb.update("Events", values, "_id=?", 575dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff new String[] {String.valueOf(eventId)}); 576ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (0 == result) { 577ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 578ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.v(TAG, "Could not update Events table with values " + values); 579ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 580ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 581ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 582ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 583ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private void cleanInstancesTable() { 584ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mDb.delete("Instances", null /* where clause */, null /* where args */); 585ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 586ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 587ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) { 588ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 589ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion); 590ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } catch (CalendarCache.CacheException e) { 591ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.e(TAG, "Could not write timezone database version in the cache"); 592ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 593ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 5949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 595ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio /** 596ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio * Check if we are in the same time zone 597ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio */ 598ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private boolean isSameTimezone() { 599ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio MetaData.Fields fields = mMetaData.getFields(); 600ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String localTimezone = TimeZone.getDefault().getID(); 601ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return TextUtils.equals(fields.timezone, localTimezone); 602ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 603ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 604ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio /** 605ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio * Check if the time zone database version is the same as the cached one 606ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio */ 607ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio protected boolean isSameTimezoneDatabaseVersion() { 608ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String timezoneDatabaseVersion = null; 609ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 610ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion(); 611ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } catch (CalendarCache.CacheException e) { 612ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.e(TAG, "Could not read timezone database version from the cache"); 613ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return false; 614ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 615ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion()); 616ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 617ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 61825e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio @VisibleForTesting 619ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio protected String getTimezoneDatabaseVersion() { 620ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio String timezoneDatabaseVersion = null; 621ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio try { 622ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion(); 623ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } catch (CalendarCache.CacheException e) { 624ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.e(TAG, "Could not read timezone database version from the cache"); 625ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return ""; 626ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 627ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion); 628ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio return timezoneDatabaseVersion; 629ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio } 630ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio 631ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio private void regenerateInstancesTable() { 6329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // The database timezone is different from the current timezone. 6339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Regenerate the Instances table for this month. Include events 6349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // starting at the beginning of this month. 6359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long now = System.currentTimeMillis(); 6369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 6379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(now); 6389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.monthDay = 1; 6399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.hour = 0; 6409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.minute = 0; 6419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.second = 0; 6421f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio 6439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long begin = time.normalize(true); 6449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long end = begin + MINIMUM_EXPANSION_SPAN; 6451f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio 6461f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio Cursor cursor = null; 6471f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio try { 6481f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio cursor = handleInstanceQuery(new SQLiteQueryBuilder(), 6491f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio begin, end, 6501f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio new String[] { Instances._ID }, 6511f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio null /* selection */, null /* sort */, false /* searchByDayInsteadOfMillis */); 6521f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio } finally { 6531f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio if (cursor != null) { 6541f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio cursor.close(); 6551f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio } 6561f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio } 6579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rescheduleMissedAlarms(); 6599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void rescheduleMissedAlarms() { 6629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff AlarmManager manager = getAlarmManager(); 6639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (manager != null) { 6649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Context context = getContext(); 6659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentResolver cr = context.getContentResolver(); 6669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff CalendarAlerts.rescheduleMissedAlarms(cr, context, manager); 6679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 6719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Appends comma separated ids. 6729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param ids Should not be empty 6739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 6749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void appendIds(StringBuilder sb, HashSet<Long> ids) { 6759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (long id : ids) { 6769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sb.append(id).append(','); 6779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sb.setLength(sb.length() - 1); // Yank the last comma 6809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 6839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected void notifyChange() { 6849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Note that semantics are changed: notification is for CONTENT_URI, not the specific 6859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Uri that was modified. 6869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff getContext().getContentResolver().notifyChange(Calendar.CONTENT_URI, null, 6879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff true /* syncToNetwork */); 6889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 6919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 6929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String sortOrder) { 693ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 694ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio Log.v(TAG, "query uri - " + uri); 6959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 6969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final SQLiteDatabase db = mDbHelper.getReadableDatabase(); 6989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 6999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 7009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String groupBy = null; 7019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String limit = null; // Not currently implemented 7029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 7039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final int match = sUriMatcher.match(uri); 7049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff switch (match) { 7059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE: 7069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return mDbHelper.getSyncState().query(db, projection, selection, selectionArgs, 7079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sortOrder); 7089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 7099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS: 7101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 7119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sEventsProjectionMap); 712595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff appendAccountFromParameter(qb, uri); 7139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 7149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS_ID: 7151ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 7169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sEventsProjectionMap); 717636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1)); 718636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("_id=?"); 7199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 72019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana 72119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana case EVENT_ENTITIES: 72219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 72319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana qb.setProjectionMap(sEventEntitiesProjectionMap); 724595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff appendAccountFromParameter(qb, uri); 72519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana break; 72619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana case EVENT_ENTITIES_ID: 72719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 72819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana qb.setProjectionMap(sEventEntitiesProjectionMap); 729636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1)); 730636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("_id=?"); 73119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana break; 73219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana 7339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS: 73443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio case CALENDAR_ENTITIES: 7359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setTables("Calendars"); 736595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff appendAccountFromParameter(qb, uri); 7379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 7389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS_ID: 73943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio case CALENDAR_ENTITIES_ID: 7409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setTables("Calendars"); 741636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1)); 742636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("_id=?"); 7439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES: 7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES_BY_DAY: 7469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long begin; 7479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long end; 7489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 7499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff begin = Long.valueOf(uri.getPathSegments().get(2)); 7509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (NumberFormatException nfe) { 7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Cannot parse begin " 7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + uri.getPathSegments().get(2)); 7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 7549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff end = Long.valueOf(uri.getPathSegments().get(3)); 7569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (NumberFormatException nfe) { 7579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Cannot parse end " 7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + uri.getPathSegments().get(3)); 7599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 7609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return handleInstanceQuery(qb, begin, end, projection, 7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selection, sortOrder, match == INSTANCES_BY_DAY); 76281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang case INSTANCES_SEARCH: 76381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang case INSTANCES_SEARCH_BY_DAY: 76481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang try { 76581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang begin = Long.valueOf(uri.getPathSegments().get(2)); 76681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } catch (NumberFormatException nfe) { 76781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang throw new IllegalArgumentException("Cannot parse begin " 76881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang + uri.getPathSegments().get(2)); 76981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 77081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang try { 77181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang end = Long.valueOf(uri.getPathSegments().get(3)); 77281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } catch (NumberFormatException nfe) { 77381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang throw new IllegalArgumentException("Cannot parse end " 77481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang + uri.getPathSegments().get(3)); 77581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 77681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // this is already decoded 77781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String query = uri.getPathSegments().get(4); 77881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang return handleInstanceSearchQuery(qb, begin, end, query, projection, 77981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang selection, sortOrder, match == INSTANCES_SEARCH_BY_DAY); 7806db535b458146a279bebd4a51d56c1bdfc204528Erik case EVENT_DAYS: 7819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int startDay; 7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int endDay; 7839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 7849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff startDay = Integer.valueOf(uri.getPathSegments().get(2)); 7859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (NumberFormatException nfe) { 7869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Cannot parse start day " 7879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + uri.getPathSegments().get(2)); 7889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 7899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 7909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff endDay = Integer.valueOf(uri.getPathSegments().get(3)); 7919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (NumberFormatException nfe) { 7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Cannot parse end day " 7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + uri.getPathSegments().get(3)); 7949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 7956db535b458146a279bebd4a51d56c1bdfc204528Erik return handleEventDayQuery(qb, startDay, endDay, projection, selection); 7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case ATTENDEES: 7971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables("Attendees, Events"); 7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sAttendeesProjectionMap); 7991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.appendWhere("Events._id=Attendees.event_id"); 8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case ATTENDEES_ID: 8021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables("Attendees, Events"); 8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sAttendeesProjectionMap); 804636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1)); 805636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("Attendees._id=? AND Events._id=Attendees.event_id"); 8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS: 8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setTables("Reminders"); 8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS_ID: 8111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables("Reminders, Events"); 8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sRemindersProjectionMap); 813636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment()); 814636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("Reminders._id=? AND Events._id=Reminders.event_id"); 8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS: 817e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS); 8189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sCalendarAlertsProjectionMap); 819e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS + 820e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff "._id=CalendarAlerts.event_id"); 8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_BY_INSTANCE: 823e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS); 8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sCalendarAlertsProjectionMap); 825e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS + 826e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff "._id=CalendarAlerts.event_id"); 8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN; 8289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_ID: 830e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS); 8319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sCalendarAlertsProjectionMap); 832636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment()); 833e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS + 834e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff "._id=CalendarAlerts.event_id AND CalendarAlerts._id=?"); 8359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EXTENDED_PROPERTIES: 8379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setTables("ExtendedProperties"); 8389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EXTENDED_PROPERTIES_ID: 8407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff qb.setTables("ExtendedProperties"); 841636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1)); 842636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff qb.appendWhere("ExtendedProperties._id=?"); 8439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff default: 8459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Unknown URL " + uri); 8469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 8479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 8489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // run the query 8499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit); 8509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 8519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection, 8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String selection, String[] selectionArgs, String sortOrder, String groupBy, 8549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String limit) { 855ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio 856ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 857ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) + 858ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio " selection: " + selection + 859ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio " selectionArgs: " + Arrays.toString(selectionArgs) + 860ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio " sortOrder: " + sortOrder + 861ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio " groupBy: " + groupBy + 862ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio " limit: " + limit); 863ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio } 8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null, 8659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sortOrder, limit); 8669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (c != null) { 8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: is this the right notification Uri? 8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff c.setNotificationUri(getContext().getContentResolver(), Calendar.Events.CONTENT_URI); 8699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return c; 8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /* 8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Fills the Instances table, if necessary, for the given range and then 8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * queries the Instances table. 8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param qb The query 8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param rangeBegin start of range (Julian days or ms) 8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param rangeEnd end of range (Julian days or ms) 8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param projection The projection 8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param selection The selection 8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param sort How to sort 8839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param searchByDay if true, range is in Julian days, if false, range is in ms 8849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @return 8859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 8869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin, 8879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long rangeEnd, String[] projection, 8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String selection, String sort, boolean searchByDay) { 8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 89081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.setTables(INSTANCE_QUERY_TABLES); 8919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sInstancesProjectionMap); 8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (searchByDay) { 8939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Convert the first and last Julian day range to a range that uses 8949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // UTC milliseconds. 8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 8969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long beginMs = time.setJulianDay((int) rangeBegin); 8979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We add one to lastDay because the time is set to 12am on the given 8989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Julian day and we want to include all the events on the last day. 8999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long endMs = time.setJulianDay((int) rangeEnd + 1); 9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // will lock the database. 9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */); 90281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.appendWhere(BETWEEN_DAY_WHERE); 9039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // will lock the database. 9059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */); 90681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.appendWhere(BETWEEN_WHERE); 9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 9088335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff String selectionArgs[] = new String[] {String.valueOf(rangeEnd), 9098335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff String.valueOf(rangeBegin)}; 9108335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */, 9117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* having */, sort); 9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 91481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang /** 915dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * Escape any special characters in the search token 916dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * @param token the token to escape 917dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * @return the escaped token 918dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang */ 919dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang @VisibleForTesting 920dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang String escapeSearchToken(String token) { 921dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token); 922dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1"); 923dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang } 924dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang 925dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang /** 92681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * Splits the search query into individual search tokens based on whitespace 927dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * and punctuation. Leaves both single quoted and double quoted strings 928dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang * intact. 92981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * 93081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * @param query the search query 93181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * @return an array of tokens from the search query 93281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang */ 93381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang @VisibleForTesting 93481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String[] tokenizeSearchQuery(String query) { 935dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang List<String> matchList = new ArrayList<String>(); 936dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query); 937dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang String token; 938dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang while (matcher.find()) { 939dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang if (matcher.group(1) != null) { 940dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang // double quoted string 941dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang token = matcher.group(1); 942dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang } else { 943dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang // unquoted token 944dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang token = matcher.group(); 945dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang } 946dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang matchList.add(escapeSearchToken(token)); 947dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang } 948dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang return matchList.toArray(new String[matchList.size()]); 94981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 95081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 95181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang /** 95281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * In order to support what most people would consider a reasonable 95381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * search behavior, we have to do some interesting things here. We 95481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * assume that when a user searches for something like "lunch meeting", 95581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * they really want any event that matches both "lunch" and "meeting", 95681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * not events that match the string "lunch meeting" itself. In order to 95781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * do this across multiple columns, we have to construct a WHERE clause 95881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * that looks like: 95981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * <code> 96081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * WHERE (title LIKE "%lunch%" 96181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * OR description LIKE "%lunch%" 96281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * OR eventLocation LIKE "%lunch%") 96381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * AND (title LIKE "%meeting%" 96481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * OR description LIKE "%meeting%" 96581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * OR eventLocation LIKE "%meeting%") 96681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * </code> 96781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * This "product of clauses" is a bit ugly, but produced a fairly good 96881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang * approximation of full-text search across multiple columns. 96981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang */ 97081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang @VisibleForTesting 97181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String constructSearchWhere(String[] tokens) { 97281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang if (tokens.length == 0) { 97381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang return ""; 97481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 97581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang StringBuilder sb = new StringBuilder(); 97681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String column, token; 97781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang for (int j = 0; j < tokens.length; j++) { 97881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang sb.append("("); 97981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang for (int i = 0; i < SEARCH_COLUMNS.length; i++) { 98081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang sb.append(SEARCH_COLUMNS[i]); 981dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang sb.append(" LIKE ? ESCAPE \""); 982dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang sb.append(SEARCH_ESCAPE_CHAR); 983dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang sb.append("\" "); 98481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang if (i < SEARCH_COLUMNS.length - 1) { 98581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang sb.append("OR "); 98681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 98781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 98818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang sb.append(")"); 98918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang if (j < tokens.length - 1) { 99018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang sb.append(" AND "); 99118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang } 99281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 99381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang return sb.toString(); 99481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 99581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 99681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang @VisibleForTesting 99781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) { 99818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang int numCols = SEARCH_COLUMNS.length; 99918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang int numArgs = tokens.length * numCols + 2; 100081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // the additional two elements here are for begin/end time 100118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang String[] selectionArgs = new String[numArgs]; 100218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang selectionArgs[0] = String.valueOf(rangeEnd); 100318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang selectionArgs[1] = String.valueOf(rangeBegin); 100481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang for (int j = 0; j < tokens.length; j++) { 100518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang for (int i = 2; i < numArgs; i++) { 100618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang selectionArgs[i] = "%" + tokens[j] + "%"; 100781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 100881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 100981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang return selectionArgs; 101081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 101181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 101281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb, 101381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang long rangeBegin, long rangeEnd, String query, String[] projection, 101481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String selection, String sort, boolean searchByDay) { 101518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang qb.setTables(INSTANCE_SEARCH_QUERY_TABLES); 101681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.setProjectionMap(sInstancesProjectionMap); 101781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 1018dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang String[] tokens = tokenizeSearchQuery(query); 101981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang String[] selectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd); 102018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // we pass this in as a HAVING instead of a WHERE so the filtering 102118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // happens after the grouping 1022dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang String searchWhere = constructSearchWhere(tokens); 1023dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang 102481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang if (searchByDay) { 102581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // Convert the first and last Julian day range to a range that uses 102681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // UTC milliseconds. 102781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang Time time = new Time(); 102881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang long beginMs = time.setJulianDay((int) rangeBegin); 102981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // We add one to lastDay because the time is set to 12am on the given 103081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // Julian day and we want to include all the events on the last day. 103181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang long endMs = time.setJulianDay((int) rangeEnd + 1); 103281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // will lock the database. 103318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // we expand the instances here because we might be searching over 103418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // a range where instance expansion has not occurred yet 103581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */); 103681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.appendWhere(BETWEEN_DAY_WHERE); 103781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } else { 103881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang // will lock the database. 103918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // we expand the instances here because we might be searching over 104018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang // a range where instance expansion has not occurred yet 104181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */); 104281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.appendWhere(BETWEEN_WHERE); 104381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 104481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 104518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang return qb.query(mDb, projection, selection, selectionArgs, 104618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang Instances._ID /* groupBy */, searchWhere /* having */, sort); 104781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang } 104881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang 10496db535b458146a279bebd4a51d56c1bdfc204528Erik private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end, 10506db535b458146a279bebd4a51d56c1bdfc204528Erik String[] projection, String selection) { 105181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.setTables(INSTANCE_QUERY_TABLES); 10526db535b458146a279bebd4a51d56c1bdfc204528Erik qb.setProjectionMap(sInstancesProjectionMap); 105343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff // Convert the first and last Julian day range to a range that uses 105443556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff // UTC milliseconds. 105543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff Time time = new Time(); 1056192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank long beginMs = time.setJulianDay(begin); 105743556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff // We add one to lastDay because the time is set to 12am on the given 105843556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff // Julian day and we want to include all the events on the last day. 1059192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank long endMs = time.setJulianDay(end + 1); 106043556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff 106143556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff acquireInstanceRange(beginMs, endMs, true); 106281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang qb.appendWhere(BETWEEN_DAY_WHERE); 10638335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)}; 10648335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff 10658335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff return qb.query(mDb, projection, selection, selectionArgs, 10666db535b458146a279bebd4a51d56c1bdfc204528Erik Instances.START_DAY /* groupBy */, null /* having */, null); 10679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 10689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 10699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 10709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Ensure that the date range given has all elements in the instance 10719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * table. Acquires the database lock and calls {@link #acquireInstanceRangeLocked}. 10729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 10739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param begin start of range (ms) 10749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param end end of range (ms) 10759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN 10769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 10779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void acquireInstanceRange(final long begin, 10789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final long end, 10799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final boolean useMinimumExpansionWindow) { 10809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.beginTransaction(); 10819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 10829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow); 10839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.setTransactionSuccessful(); 10849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 10859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.endTransaction(); 10869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 10879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 10889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 10899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 10909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Ensure that the date range given has all elements in the instance 10919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * table. The database lock must be held when calling this method. 10929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 10939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param begin start of range (ms) 10949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param end end of range (ms) 10959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN 10969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 10979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void acquireInstanceRangeLocked(long begin, long end, 10989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean useMinimumExpansionWindow) { 10999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long expandBegin = begin; 11009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long expandEnd = end; 11019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (useMinimumExpansionWindow) { 11039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // if we end up having to expand events into the instances table, expand 11049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // events for a minimal amount of time, so we do not have to perform 11059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // expansions frequently. 11069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long span = end - begin; 11079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (span < MINIMUM_EXPANSION_SPAN) { 11089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2; 11099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff expandBegin -= additionalRange; 11109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff expandEnd += additionalRange; 11119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Check if the timezone has changed. 11159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We do this check here because the database is locked and we can 11169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // safely delete all the entries in the Instances table. 11179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff MetaData.Fields fields = mMetaData.getFieldsLocked(); 11189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String dbTimezone = fields.timezone; 11199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long maxInstance = fields.maxInstance; 11209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long minInstance = fields.minInstance; 11219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String localTimezone = TimeZone.getDefault().getID(); 11229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean timezoneChanged = (dbTimezone == null) || !dbTimezone.equals(localTimezone); 11239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (maxInstance == 0 || timezoneChanged) { 11259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Empty the Instances table and expand from scratch. 11269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.execSQL("DELETE FROM Instances;"); 11279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Config.LOGV) { 11286db535b458146a279bebd4a51d56c1bdfc204528Erik Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances," 11299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " timezone changed: " + timezoneChanged); 11309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff expandInstanceRangeLocked(expandBegin, expandEnd, localTimezone); 11329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1133dbd797d59294d72d7ea9226d10128674b634aaadErik mMetaData.writeLocked(localTimezone, expandBegin, expandEnd); 11349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 11359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the desired range [begin, end] has already been 11389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // expanded, then simply return. The range is inclusive, that is, 11399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // events that touch either endpoint are included in the expansion. 11409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // This means that a zero-duration event that starts and ends at 11419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the endpoint will be included. 11429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We use [begin, end] here and not [expandBegin, expandEnd] for 11439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // checking the range because a common case is for the client to 11449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // request successive days or weeks, for example. If we checked 11459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that the expanded range [expandBegin, expandEnd] then we would 11469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // always be expanding because there would always be one more day 11479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // or week that hasn't been expanded. 11489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if ((begin >= minInstance) && (end <= maxInstance)) { 11499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Config.LOGV) { 11509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd 11519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + ") falls within previously expanded range."); 11529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 11549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the requested begin point has not been expanded, then include 11579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // more events than requested in the expansion (use "expandBegin"). 11589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (begin < minInstance) { 11599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff expandInstanceRangeLocked(expandBegin, minInstance, localTimezone); 11609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff minInstance = expandBegin; 11619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the requested end point has not been expanded, then include 11649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // more events than requested in the expansion (use "expandEnd"). 11659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (end > maxInstance) { 11669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff expandInstanceRangeLocked(maxInstance, expandEnd, localTimezone); 11679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff maxInstance = expandEnd; 11689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Update the bounds on the Instances table. 1171dbd797d59294d72d7ea9226d10128674b634aaadErik mMetaData.writeLocked(localTimezone, minInstance, maxInstance); 11729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 11739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final String[] EXPAND_COLUMNS = new String[] { 11759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events._ID, 11769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events._SYNC_ID, 11779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.STATUS, 11789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.DTSTART, 11799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.DTEND, 11809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.EVENT_TIMEZONE, 11819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.RRULE, 11829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.RDATE, 11839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.EXRULE, 11849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.EXDATE, 11859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.DURATION, 11869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.ALL_DAY, 11879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Events.ORIGINAL_EVENT, 11881030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff Events.ORIGINAL_INSTANCE_TIME, 11891dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio Events.CALENDAR_ID, 11901b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio Events.DELETED 11919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff }; 11929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 11949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Make instances for the given range. 11959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 11969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void expandInstanceRangeLocked(long begin, long end, String localTimezone) { 11979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 11989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (PROFILE) { 11999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Debug.startMethodTracing("expandInstanceRangeLocked"); 12009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.VERBOSE)) { 12039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "Expanding events between " + begin + " and " + end); 12049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor entries = getEntries(begin, end); 12079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 12089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff performInstanceExpansion(begin, end, localTimezone, entries); 12099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 12109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (entries != null) { 12119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff entries.close(); 12129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (PROFILE) { 12159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Debug.stopMethodTracing(); 12169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 12209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Get all entries affecting the given window. 12219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param begin Window start (ms). 12229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param end Window end (ms). 12239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @return Cursor for the entries; caller must close it. 12249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 12259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private Cursor getEntries(long begin, long end) { 12269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 12271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 12289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sEventsProjectionMap); 12299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String beginString = String.valueOf(begin); 12319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String endString = String.valueOf(end); 12329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // grab recurrence exceptions that fall outside our expansion window but modify 12349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // recurrences that do fall within our window. we won't insert these into the output 12359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // set of instances, but instead will just add them to our cancellations list, so we 12369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // can cancel the correct recurrence expansion instances. 12379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // we don't have originalInstanceDuration or end time. for now, assume the original 12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // instance lasts no longer than 1 week. 12392d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio // also filter with syncable state (we dont want the entries from a non syncable account) 12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: compute the originalInstanceEndTime or get this from the server. 12412d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio qb.appendWhere("((dtstart <= ? AND (lastDate IS NULL OR lastDate >= ?)) OR " + 12428335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff "(originalInstanceTime IS NOT NULL AND originalInstanceTime <= ? AND " + 12432d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio "originalInstanceTime >= ?)) AND (sync_events != 0)"); 12448335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff String selectionArgs[] = new String[] {endString, beginString, endString, 12458335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff String.valueOf(begin - MAX_ASSUMED_DURATION)}; 1246e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff Cursor c = qb.query(mDb, EXPAND_COLUMNS, null /* selection */, 12478335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff selectionArgs, null /* groupBy */, 12487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* having */, null /* sortOrder */); 1249e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (Log.isLoggable(TAG, Log.VERBOSE)) { 1250e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff Log.v(TAG, "Instance expansion: got " + c.getCount() + " entries"); 1251e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 1252e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff return c; 12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 12549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 12561030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * Generates a unique key from the syncId and calendarId. 12571030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * The purpose of this is to prevent collisions if two different calendars use the 12581030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * same sync id. This can happen if a Google calendar is accessed by two different accounts, 12591030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * or with Exchange, where ids are not unique between calendars. 12601030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * @param syncId Id for the event 12611030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * @param calendarId Id for the calendar 12621030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff * @return key 12631030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff */ 12641030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff private String getSyncIdKey(String syncId, long calendarId) { 12651030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff return calendarId + ":" + syncId; 12661030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff } 12671030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff 12681030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff /** 12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Perform instance expansion on the given entries. 12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param begin Window start (ms). 12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param end Window end (ms). 12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param localTimezone 12739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param entries The entries to process. 12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void performInstanceExpansion(long begin, long end, String localTimezone, 12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor entries) { 12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff RecurrenceProcessor rp = new RecurrenceProcessor(); 12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12791030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // Key into the instance values to hold the original event concatenated with calendar id. 12801030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff final String ORIGINAL_EVENT_AND_CALENDAR = "ORIGINAL_EVENT_AND_CALENDAR"; 12811030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff 12829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int statusColumn = entries.getColumnIndex(Events.STATUS); 12839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int dtstartColumn = entries.getColumnIndex(Events.DTSTART); 12849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int dtendColumn = entries.getColumnIndex(Events.DTEND); 12859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE); 12869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int durationColumn = entries.getColumnIndex(Events.DURATION); 12879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int rruleColumn = entries.getColumnIndex(Events.RRULE); 12889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int rdateColumn = entries.getColumnIndex(Events.RDATE); 12899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int exruleColumn = entries.getColumnIndex(Events.EXRULE); 12909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int exdateColumn = entries.getColumnIndex(Events.EXDATE); 12919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int allDayColumn = entries.getColumnIndex(Events.ALL_DAY); 12929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int idColumn = entries.getColumnIndex(Events._ID); 12939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID); 12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT); 12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME); 12961030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff int calendarIdColumn = entries.getColumnIndex(Events.CALENDAR_ID); 12971b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio int deletedColumn = entries.getColumnIndex(Events.DELETED); 12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues initialValues; 13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff EventInstancesMap instancesMap = new EventInstancesMap(); 13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Duration duration = new Duration(); 13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time eventTime = new Time(); 13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Invariant: entries contains all events that affect the current 13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // window. It consists of: 13079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // a) Individual events that fall in the window. These will be 13089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // displayed. 13099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // b) Recurrences that included the window. These will be displayed 13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // if not canceled. 13119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // c) Recurrence exceptions that fall in the window. These will be 13129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // displayed if not cancellations. 13139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // d) Recurrence exceptions that modify an instance inside the 13149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // window (subject to 1 week assumption above), but are outside 13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the window. These will not be displayed. Cases c and d are 13169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // distingushed by the start / end time. 13179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff while (entries.moveToNext()) { 13199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues = null; 13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean allDay = entries.getInt(allDayColumn) != 0; 13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String eventTimezone = entries.getString(eventTimezoneColumn); 13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay || TextUtils.isEmpty(eventTimezone)) { 13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // in the events table, allDay events start at midnight. 13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // this forces them to stay at midnight for all day events 13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: check that this actually does the right thing. 13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTimezone = Time.TIMEZONE_UTC; 13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long dtstartMillis = entries.getLong(dtstartColumn); 13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long eventId = Long.valueOf(entries.getLong(idColumn)); 13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String durationStr = entries.getString(durationColumn); 13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (durationStr != null) { 13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.parse(durationStr); 13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 13409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff catch (DateException e) { 13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "error parsing duration for event " 13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + eventId + "'" + durationStr + "'", e); 13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.sign = 1; 13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.weeks = 0; 13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.days = 0; 13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.hours = 0; 13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.minutes = 0; 13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.seconds = 0; 13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff durationStr = "+P0S"; 13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String syncId = entries.getString(syncIdColumn); 13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String originalEvent = entries.getString(originalEventColumn); 13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long originalInstanceTimeMillis = -1; 13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!entries.isNull(originalInstanceTimeColumn)) { 13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn); 13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int status = entries.getInt(statusColumn); 13611dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio boolean deleted = (entries.getInt(deletedColumn) != 0); 13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String rruleStr = entries.getString(rruleColumn); 13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String rdateStr = entries.getString(rdateColumn); 13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String exruleStr = entries.getString(exruleColumn); 13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String exdateStr = entries.getString(exdateColumn); 13671030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff long calendarId = entries.getLong(calendarIdColumn); 13681030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff String syncIdKey = getSyncIdKey(syncId, calendarId); // key into instancesMap 13699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1370f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio RecurrenceSet recur = null; 1371f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio try { 1372f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr); 1373f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio } catch (EventRecurrence.InvalidFormatException e) { 1374f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio Log.w(TAG, "Could not parse RRULE recurrence string: " + rruleStr, e); 1375f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio continue; 1376f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio } 13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1378f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio if (null != recur && recur.hasRecurrence()) { 13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the event is repeating 13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (status == Events.STATUS_CANCELED) { 13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // should not happen! 13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "Found canceled recurring event in " 13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "Events table. Ignoring."); 13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1387370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik if (deleted) { 1388370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik // Don't expand deleted recurring events 1389370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik continue; 1390370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik } 13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // need to parse the event into a local calendar. 13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.timezone = eventTimezone; 13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.set(dtstartMillis); 13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.allDay = allDay; 13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 13979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (durationStr == null) { 13989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // should not happen. 13999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "Repeating event has no duration -- " 14009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "should not happen."); 14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay) { 14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // set to one day. 14039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.sign = 1; 14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.weeks = 0; 14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.days = 1; 14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.hours = 0; 14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.minutes = 0; 14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.seconds = 0; 14099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff durationStr = "+P1D"; 14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // compute the duration from dtend, if we can. 14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // otherwise, use 0s. 14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.sign = 1; 14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.weeks = 0; 14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.days = 0; 14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.hours = 0; 14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.minutes = 0; 14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!entries.isNull(dtendColumn)) { 14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long dtendMillis = entries.getLong(dtendColumn); 14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000); 14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff durationStr = "+P" + duration.seconds + "S"; 14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.seconds = 0; 14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff durationStr = "+P0S"; 14259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long[] dates; 14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff dates = rp.expand(eventTime, recur, begin, end); 14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Initialize the "eventTime" timezone outside the loop. 14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // This is used in computeTimezoneDependentFields(). 14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay) { 14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.timezone = Time.TIMEZONE_UTC; 14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.timezone = localTimezone; 14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long durationMillis = duration.getMillis(); 14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (long date : dates) { 14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues = new ContentValues(); 14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Instances.EVENT_ID, eventId); 14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Instances.BEGIN, date); 14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long dtendMillis = date + durationMillis; 14479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Instances.END, dtendMillis); 14489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff computeTimezoneDependentFields(date, dtendMillis, 14509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime, initialValues); 14511030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff instancesMap.add(syncIdKey, initialValues); 14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the event is not repeating 14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues = new ContentValues(); 14569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // if this event has an "original" field, then record 14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that we need to cancel the original event (we can't 14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // do that here because the order of this loop isn't 14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // defined) 14619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (originalEvent != null && originalInstanceTimeMillis != -1) { 14621030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // The ORIGINAL_EVENT_AND_CALENDAR holds the 14631030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // calendar id concatenated with the ORIGINAL_EVENT to form 14641030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // a unique key, matching the keys for instancesMap. 14651030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff initialValues.put(ORIGINAL_EVENT_AND_CALENDAR, 14661030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff getSyncIdKey(originalEvent, calendarId)); 14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Events.ORIGINAL_INSTANCE_TIME, 14689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff originalInstanceTimeMillis); 14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Events.STATUS, status); 14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long dtendMillis = dtstartMillis; 14739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (durationStr == null) { 14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!entries.isNull(dtendColumn)) { 14759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff dtendMillis = entries.getLong(dtendColumn); 14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff dtendMillis = duration.addTo(dtstartMillis); 14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // this non-recurring event might be a recurrence exception that doesn't 14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // actually fall within our expansion window, but instead was selected 14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // so we can correctly cancel expanded recurrence instances below. do not 14849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // add events to the instances map if they don't actually fall within our 14859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // expansion window. 14869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if ((dtendMillis < begin) || (dtstartMillis > end)) { 14879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (originalEvent != null && originalInstanceTimeMillis != -1) { 14889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Events.STATUS, Events.STATUS_CANCELED); 14899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 14909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "Unexpected event outside window: " + syncId); 14919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 14929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Instances.EVENT_ID, eventId); 14969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 14971dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio initialValues.put(Instances.BEGIN, dtstartMillis); 14989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff initialValues.put(Instances.END, dtendMillis); 14999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15001dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio // we temporarily store the DELETED status (will be cleaned later) 15011b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio initialValues.put(Events.DELETED, deleted); 15021dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio 15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay) { 15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.timezone = Time.TIMEZONE_UTC; 15059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 15069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime.timezone = localTimezone; 15079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff computeTimezoneDependentFields(dtstartMillis, dtendMillis, 15099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff eventTime, initialValues); 15109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15111030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff instancesMap.add(syncIdKey, initialValues); 15129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (DateException e) { 15149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "RecurrenceProcessor error ", e); 15159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (TimeFormatException e) { 15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "RecurrenceProcessor error ", e); 15179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Invariant: instancesMap contains all instances that affect the 15211030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // window, indexed by original sync id concatenated with calendar id. 15221030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // It consists of: 15239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // a) Individual events that fall in the window. They have: 15249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // EVENT_ID, BEGIN, END 15259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // b) Instances of recurrences that fall in the window. They may 15269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // be subject to exceptions. They have: 15279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // EVENT_ID, BEGIN, END 15289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // c) Exceptions that fall in the window. They have: 15291030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS (since they can 15309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // be a modification or cancellation), EVENT_ID, BEGIN, END 15319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // d) Recurrence exceptions that modify an instance inside the 15329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // window but fall outside the window. They have: 15331030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff // ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS = 15349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // STATUS_CANCELED, EVENT_ID, BEGIN, END 15359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // First, delete the original instances corresponding to recurrence 15379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // exceptions. We do this by iterating over the list and for each 15389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // recurrence exception, we search the list for an instance with a 15399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // matching "original instance time". If we find such an instance, 15409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // we remove it from the list. If we don't find such an instance 15419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // then we cancel the recurrence exception. 15429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Set<String> keys = instancesMap.keySet(); 15431030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff for (String syncIdKey : keys) { 15441030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff InstancesList list = instancesMap.get(syncIdKey); 15459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (ContentValues values : list) { 15469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If this instance is not a recurrence exception, then 15489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // skip it. 15491030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff if (!values.containsKey(ORIGINAL_EVENT_AND_CALENDAR)) { 15509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 15519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15531030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff String originalEventPlusCalendar = values.getAsString(ORIGINAL_EVENT_AND_CALENDAR); 15549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 15551030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff InstancesList originalList = instancesMap.get(originalEventPlusCalendar); 15569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (originalList == null) { 15579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // The original recurrence is not present, so don't try canceling it. 15589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 15599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Search the original event for a matching original 15629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // instance time. If there is a matching one, then remove 15639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the original one. We do this both for exceptions that 15649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // change the original instance as well as for exceptions 15659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that delete the original instance. 15669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (int num = originalList.size() - 1; num >= 0; num--) { 15679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues originalValues = originalList.get(num); 15689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long beginTime = originalValues.getAsLong(Instances.BEGIN); 15699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (beginTime == originalTime) { 15709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We found the original instance, so remove it. 15719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff originalList.remove(num); 15729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 15769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Invariant: instancesMap contains filtered instances. 15789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // It consists of: 15799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // a) Individual events that fall in the window. 15809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // b) Instances of recurrences that fall in the window and have not 15819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // been subject to exceptions. 15829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // c) Exceptions that fall in the window. They will have 15839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // STATUS_CANCELED if they are cancellations. 15849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // d) Recurrence exceptions that modify an instance inside the 15859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // window but fall outside the window. These are STATUS_CANCELED. 15869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Now do the inserts. Since the db lock is held when this method is executed, 15889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // this will be done in a transaction. 15899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db 15909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // while the calendar app is trying to query the db (expanding instances)), we will 15919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // not be "polite" and yield the lock until we're done. This will favor local query 15929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // operations over sync/write operations. 15931030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff for (String syncIdKey : keys) { 15941030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff InstancesList list = instancesMap.get(syncIdKey); 15959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (ContentValues values : list) { 15969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 15971dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio // If this instance was cancelled or deleted then don't create a new 15989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // instance. 15999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Integer status = values.getAsInteger(Events.STATUS); 16001b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio boolean deleted = values.containsKey(Events.DELETED) ? 16011b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio values.getAsBoolean(Events.DELETED) : false; 16021dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio if ((status != null && status == Events.STATUS_CANCELED) || deleted) { 16039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 16049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16061dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio // We remove this useless key (not valid in the context of Instances table) 16071b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio values.remove(Events.DELETED); 16081dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio 16099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Remove these fields before inserting a new instance 16101030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff values.remove(ORIGINAL_EVENT_AND_CALENDAR); 16119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.remove(Events.ORIGINAL_INSTANCE_TIME); 16129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.remove(Events.STATUS); 16139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1614c874ed5c6cc0fcc6ac06ae7d20db0eab7d749608Ken Shirriff mDbHelper.instancesReplace(values); 16159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 16209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Computes the timezone-dependent fields of an instance of an event and 16219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * updates the "values" map to contain those fields. 16229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 16239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param begin the start time of the instance (in UTC milliseconds) 16249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param end the end time of the instance (in UTC milliseconds) 16259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param local a Time object with the timezone set to the local timezone 16269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param values a map that will contain the timezone-dependent fields 16279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 16289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void computeTimezoneDependentFields(long begin, long end, 16299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time local, ContentValues values) { 16309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff local.set(begin); 16319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int startDay = Time.getJulianDay(begin, local.gmtoff); 16329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int startMinute = local.hour * 60 + local.minute; 16339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff local.set(end); 16359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int endDay = Time.getJulianDay(end, local.gmtoff); 16369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int endMinute = local.hour * 60 + local.minute; 16379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Special case for midnight, which has endMinute == 0. Change 16399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that to +24 hours on the previous day to make everything simpler. 16409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Exception: if start and end minute are both 0 on the same day, 16419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // then leave endMinute alone. 16429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (endMinute == 0 && endDay > startDay) { 16439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff endMinute = 24 * 60; 16449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff endDay -= 1; 16459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Instances.START_DAY, startDay); 16489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Instances.END_DAY, endDay); 16499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Instances.START_MINUTE, startMinute); 16509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Instances.END_MINUTE, endMinute); 16519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 16549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public String getType(Uri url) { 16559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int match = sUriMatcher.match(url); 16569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff switch (match) { 16579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS: 16589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.dir/event"; 16599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS_ID: 16609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.item/event"; 16619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS: 16629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.dir/reminder"; 16639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS_ID: 16649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.item/reminder"; 16659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS: 16669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.dir/calendar-alert"; 16679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_BY_INSTANCE: 16689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.dir/calendar-alert-by-instance"; 16699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_ID: 16709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.item/calendar-alert"; 16719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES: 16729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES_BY_DAY: 16736db535b458146a279bebd4a51d56c1bdfc204528Erik case EVENT_DAYS: 16749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return "vnd.android.cursor.dir/event-instance"; 167548587d3291c4db7f0942e1bff55b88cfa7764ba0Erik case TIME: 167648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik return "time/epoch"; 16779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff default: 16789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Unknown URL " + url); 16799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 16829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public static boolean isRecurrenceEvent(ContentValues values) { 16839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return (!TextUtils.isEmpty(values.getAsString(Events.RRULE))|| 16849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff !TextUtils.isEmpty(values.getAsString(Events.RDATE))|| 16859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT))); 16869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 16879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1688646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik /** 1689646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * Takes an event and corrects the hrs, mins, secs if it is an allDay event. 1690646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * 1691646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and 1692646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * corrects the fields DTSTART, DTEND, and DURATION if necessary. Also checks to ensure that 1693646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * either both DTSTART and DTEND or DTSTART and DURATION are set for each event. 1694646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * 1695646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * @param updatedValues The values to check and correct 1696646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * @return Returns true if a correction was necessary, false otherwise 1697646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik */ 1698646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik private boolean fixAllDayTime(Uri uri, ContentValues updatedValues) { 1699646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik boolean neededCorrection = false; 1700646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (updatedValues.containsKey(Events.ALL_DAY) 1701646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik && updatedValues.getAsInteger(Events.ALL_DAY).intValue() == 1) { 1702646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Long dtstart = updatedValues.getAsLong(Events.DTSTART); 1703646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Long dtend = updatedValues.getAsLong(Events.DTEND); 1704646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik String duration = updatedValues.getAsString(Events.DURATION); 1705646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Time time = new Time(); 1706646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Cursor currentTimesCursor = null; 1707646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik String tempValue; 1708646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // If a complete set of time fields doesn't exist query the db for them. A complete set 1709646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // is dtstart and dtend for non-recurring events or dtstart and duration for recurring 1710646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // events. 1711646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if(dtstart == null || (dtend == null && duration == null)) { 1712646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // Make sure we have an id to search for, if not this is probably a new event 1713646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (uri.getPathSegments().size() == 2) { 1714646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor = query(uri, 1715646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik ALLDAY_TIME_PROJECTION, 1716646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik null /* selection */, 1717646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik null /* selectionArgs */, 1718646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik null /* sort */); 1719646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (currentTimesCursor != null) { 1720646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (!currentTimesCursor.moveToFirst() || 1721646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor.getCount() != 1) { 1722646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // Either this is a new event or the query is too general to get data 1723646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // from the db. In either case don't try to use the query and catch 1724646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // errors when trying to update the time fields. 1725646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor.close(); 1726646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor = null; 1727646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1728646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1729646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1730646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1731646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 1732646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // Ensure dtstart exists for this event (always required) and set so h,m,s are 0 if 1733646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // necessary. 1734646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // TODO Move this somewhere to check all events, not just allDay events. 1735646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (dtstart == null) { 1736646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (currentTimesCursor != null) { 1737646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // getLong returns 0 for empty fields, we'd like to know if a field is empty 1738646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // so getString is used instead. 1739646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik tempValue = currentTimesCursor.getString(ALLDAY_DTSTART_INDEX); 1740646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik try { 1741646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik dtstart = Long.valueOf(tempValue); 1742646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } catch (NumberFormatException e) { 1743646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor.close(); 1744646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik throw new IllegalArgumentException("Event has no DTSTART field, the db " + 1745646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik "may be damaged. Set DTSTART for this event to fix."); 1746646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1747646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } else { 1748646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik throw new IllegalArgumentException("DTSTART cannot be empty for new events."); 1749646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1750646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1751646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.clear(Time.TIMEZONE_UTC); 1752646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.set(dtstart.longValue()); 1753646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (time.hour != 0 || time.minute != 0 || time.second != 0) { 1754646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.hour = 0; 1755646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.minute = 0; 1756646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.second = 0; 1757646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik updatedValues.put(Events.DTSTART, time.toMillis(true)); 1758646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik neededCorrection = true; 1759646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1760646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 1761646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // If dtend exists for this event make sure it's h,m,s are 0. 1762646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (dtend == null && currentTimesCursor != null) { 1763646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // getLong returns 0 for empty fields. We'd like to know if a field is empty 1764646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // so getString is used instead. 1765646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik tempValue = currentTimesCursor.getString(ALLDAY_DTEND_INDEX); 1766646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik try { 1767646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik dtend = Long.valueOf(tempValue); 1768646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } catch (NumberFormatException e) { 1769646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik dtend = null; 1770646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1771646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1772646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (dtend != null) { 1773646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.clear(Time.TIMEZONE_UTC); 1774646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.set(dtend.longValue()); 1775646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (time.hour != 0 || time.minute != 0 || time.second != 0) { 1776646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.hour = 0; 1777646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.minute = 0; 1778646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik time.second = 0; 1779646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik dtend = time.toMillis(true); 1780646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik updatedValues.put(Events.DTEND, dtend); 1781646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik neededCorrection = true; 1782646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1783646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1784646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 1785646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (currentTimesCursor != null) { 1786646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (duration == null) { 1787646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik duration = currentTimesCursor.getString(ALLDAY_DURATION_INDEX); 1788646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1789646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik currentTimesCursor.close(); 1790646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1791646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 1792646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (duration != null) { 1793646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik int len = duration.length(); 1794646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's 1795646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik * in the seconds format, and if so converts it to days. 1796646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik */ 1797646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (len == 0) { 1798646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik duration = null; 1799646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } else if (duration.charAt(0) == 'P' && 1800646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik duration.charAt(len - 1) == 'S') { 1801646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik int seconds = Integer.parseInt(duration.substring(1, len - 1)); 1802646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS; 1803646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik duration = "P" + days + "D"; 1804646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik updatedValues.put(Events.DURATION, duration); 1805646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik neededCorrection = true; 1806646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1807646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1808646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 1809646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (duration == null && dtend == null) { 1810646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " + 1811646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik "an event."); 1812646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1813646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1814646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik return neededCorrection; 1815646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 1816646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik 18179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 18189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected Uri insertInTransaction(Uri uri, ContentValues values) { 1819ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 18209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "insertInTransaction: " + uri); 18219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 18239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final boolean callerIsSyncAdapter = 18249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false); 18259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 18269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final int match = sUriMatcher.match(uri); 18279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long id = 0; 18289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 18299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff switch (match) { 18309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE: 18319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.getSyncState().insert(mDb, values); 18329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 18339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS: 18347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (!callerIsSyncAdapter) { 18357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff values.put(Events._SYNC_DIRTY, 1); 18367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 18379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(Events.DTSTART)) { 18389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new RuntimeException("DTSTART field missing from event"); 18399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: do we really need to make a copy? 1841e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff ContentValues updatedValues = new ContentValues(values); 1842e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff validateEventData(updatedValues); 1843e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // updateLastDate must be after validation, to ensure proper last date computation 1844e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff updatedValues = updateLastDate(updatedValues); 18459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (updatedValues == null) { 18469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new RuntimeException("Could not insert event."); 18479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // return null; 18489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String owner = null; 18509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (updatedValues.containsKey(Events.CALENDAR_ID) && 18519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff !updatedValues.containsKey(Events.ORGANIZER)) { 18529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID)); 18539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: This isn't entirely correct. If a guest is adding a recurrence 18549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // exception to an event, the organizer should stay the original organizer. 18559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // This value doesn't go to the server and it will get fixed on sync, 18569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // so it shouldn't really matter. 18579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (owner != null) { 18589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updatedValues.put(Events.ORGANIZER, owner); 18599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1861646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (fixAllDayTime(uri, updatedValues)) { 1862646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Log.w(TAG, "insertInTransaction: " + 1863646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik "allDay is true but sec, min, hour were not 0."); 1864646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 18659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.eventsInsert(updatedValues); 18669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (id != -1) { 18679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateEventRawTimesLocked(id, updatedValues); 18689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateInstancesLocked(updatedValues, id, true /* new event */, mDb); 18699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 18709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If we inserted a new event that specified the self-attendee 18719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // status, then we need to add an entry to the attendees table. 18729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) { 18739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS); 18749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (owner == null) { 18759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID)); 18769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff createAttendeeEntry(id, status, owner); 18789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 1879dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(id, callerIsSyncAdapter); 18809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 18829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS: 18839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS); 18849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (syncEvents != null && syncEvents == 1) { 18859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String accountName = values.getAsString(Calendars._SYNC_ACCOUNT); 18869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String accountType = values.getAsString( 18879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Calendars._SYNC_ACCOUNT_TYPE); 18889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final Account account = new Account(accountName, accountType); 18891b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio String eventsUrl = values.getAsString(Calendars.SYNC1); 18901b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl); 18919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 18929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.calendarsInsert(values); 1893dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(id, callerIsSyncAdapter); 18949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 18959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case ATTENDEES: 18969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(Attendees.EVENT_ID)) { 18979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Attendees values must " 18989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "contain an event_id"); 18999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.attendeesInsert(values); 19017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (!callerIsSyncAdapter) { 19027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff setEventDirty(values.getAsInteger(Attendees.EVENT_ID)); 19037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 19049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 19059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Copy the attendee status value to the Events table. 19069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateEventAttendeeStatus(mDb, values); 19079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 19089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS: 19099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(Reminders.EVENT_ID)) { 19109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Reminders values must " 19119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "contain an event_id"); 19129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.remindersInsert(values); 19147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (!callerIsSyncAdapter) { 19157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff setEventDirty(values.getAsInteger(Reminders.EVENT_ID)); 19167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 19179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 19189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Schedule another event alarm, if necessary 19199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 19209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "insertInternal() changing reminder"); 19219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 19239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 19249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS: 19259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(CalendarAlerts.EVENT_ID)) { 19269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("CalendarAlerts values must " 19279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "contain an event_id"); 19289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.calendarAlertsInsert(values); 19302fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // Note: dirty bit is not set for Alerts because it is not synced. 19312fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // It is generated from Reminders, which is synced. 19329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 19339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EXTENDED_PROPERTIES: 19349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(Calendar.ExtendedProperties.EVENT_ID)) { 19359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("ExtendedProperties values must " 19369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "contain an event_id"); 19379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff id = mDbHelper.extendedPropertiesInsert(values); 19397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (!callerIsSyncAdapter) { 19407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff setEventDirty(values.getAsInteger(Calendar.ExtendedProperties.EVENT_ID)); 19417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 19429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 19439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case DELETED_EVENTS: 19449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS_ID: 19459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS_ID: 19469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_ID: 19479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EXTENDED_PROPERTIES_ID: 19489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES: 19499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES_BY_DAY: 19506db535b458146a279bebd4a51d56c1bdfc204528Erik case EVENT_DAYS: 19517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff throw new UnsupportedOperationException("Cannot insert into that URL: " + uri); 19529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff default: 19539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Unknown URL " + uri); 19549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 19569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (id < 0) { 19579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return null; 19589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 19609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return ContentUris.withAppendedId(uri, id); 19619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 19629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 1963e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff /** 1964e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * Do some validation on event data before inserting. 1965e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * In particular make sure dtend, duration, etc make sense for 1966e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * the type of event (regular, recurrence, exception). Remove 1967e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * any unexpected fields. 1968e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * 1969e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * @param values the ContentValues to insert 1970e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff */ 1971e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff private void validateEventData(ContentValues values) { 1972e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasDtend = values.getAsLong(Events.DTEND) != null; 1973e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION)); 1974e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE)); 1975e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE)); 1976e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT)); 1977e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null; 1978e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (hasRrule || hasRdate) { 1979e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // Recurrence: 1980e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtstart is start time of first event 1981e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtend is null 1982e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // duration is the duration of the event 1983e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // rrule is the recurrence rule 1984e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // lastDate is the end of the last event or null if it repeats forever 1985e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalEvent is null 1986e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalInstanceTime is null 1987e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) { 1988e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 1989e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff Log.e(TAG, "Invalid values for recurrence: " + values); 1990e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 1991e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff values.remove(Events.DTEND); 1992e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff values.remove(Events.ORIGINAL_EVENT); 1993e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff values.remove(Events.ORIGINAL_INSTANCE_TIME); 1994e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 1995e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } else if (hasOriginalEvent || hasOriginalInstanceTime) { 1996e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // Recurrence exception 1997e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtstart is start time of exception event 1998e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtend is end time of exception event 1999e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // duration is null 2000e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // rrule is null 2001e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // lastdate is same as dtend 2002e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalEvent is the _sync_id of the recurrence 2003e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalInstanceTime is the start time of the event being replaced 2004e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) { 2005e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 2006e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff Log.e(TAG, "Invalid values for recurrence exception: " + values); 2007e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2008e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff values.remove(Events.DURATION); 2009e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2010e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } else { 2011e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // Regular event 2012e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtstart is the start time 2013e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // dtend is the end time 2014e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // duration is null 2015e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // rrule is null 2016e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // lastDate is the same as dtend 2017e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalEvent is null 2018e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // originalInstanceTime is null 2019e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (!hasDtend || hasDuration) { 2020e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 2021e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff Log.e(TAG, "Invalid values for event: " + values); 2022e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2023e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff values.remove(Events.DURATION); 2024e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2025e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2026e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff } 2027e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff 20287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private void setEventDirty(int eventId) { 2029636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=?", new Integer[] {eventId}); 20307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 20317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 20329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 20339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Gets the calendar's owner for an event. 20349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param calId 20359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @return email of owner or null 20369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 20379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private String getOwner(long calId) { 2038f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio if (calId < 0) { 2039f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio Log.e(TAG, "Calendar Id is not valid: " + calId); 2040f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio return null; 2041f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio } 20429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the email address of this user from this Calendar 20439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String emailAddress = null; 20449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor cursor = null; 20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId), 20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff new String[] { Calendars.OWNER_ACCOUNT }, 20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selection */, 20499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selectionArgs */, 20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* sort */); 20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor == null || !cursor.moveToFirst()) { 20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "Couldn't find " + calId + " in Calendars table"); 20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return null; 20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff emailAddress = cursor.getString(0); 20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor != null) { 20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 20609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 20619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return emailAddress; 20629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 20639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 20649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 20659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Creates an entry in the Attendees table that refers to the given event 20669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * and that has the given response status. 20679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param eventId the event id that the new entry in the Attendees table 20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * should refer to 20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param status the response status 20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param emailAddress the email of the attendee 20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 20739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void createAttendeeEntry(long eventId, int status, String emailAddress) { 20749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues values = new ContentValues(); 20759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Attendees.EVENT_ID, eventId); 20769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Attendees.ATTENDEE_STATUS, status); 20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE); 20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: The relationship could actually be ORGANIZER, but it will get straightened out 20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // on sync. 20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Attendees.ATTENDEE_RELATIONSHIP, 20819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Attendees.RELATIONSHIP_ATTENDEE); 20829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Attendees.ATTENDEE_EMAIL, emailAddress); 20839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // We don't know the ATTENDEE_NAME but that will be filled in by the 20859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // server and sent back to us. 20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDbHelper.attendeesInsert(values); 20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 20899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 20909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Updates the attendee status in the Events table to be consistent with 20919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * the value in the Attendees table. 20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param db the database 20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param attendeeValues the column values for one row in the Attendees 20959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * table. 20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 20979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) { 20989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the event id for this attendee 20999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID); 21009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (MULTIPLE_ATTENDEES_PER_EVENT) { 21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the calendar id for this event 21039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor cursor = null; 21049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long calId; 21059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), 21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff new String[] { Events.CALENDAR_ID }, 21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selection */, 21099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selectionArgs */, 21109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* sort */); 21119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor == null || !cursor.moveToFirst()) { 21129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "Couldn't find " + eventId + " in Events table"); 21139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 21149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff calId = cursor.getLong(0); 21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 21179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor != null) { 21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the owner email for this Calendar 21239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String calendarEmail = null; 21249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor = null; 21259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 21269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId), 21279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff new String[] { Calendars.OWNER_ACCOUNT }, 21289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selection */, 21299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selectionArgs */, 21309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* sort */); 21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor == null || !cursor.moveToFirst()) { 21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "Couldn't find " + calId + " in Calendars table"); 21339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 21349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff calendarEmail = cursor.getString(0); 21369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 21379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor != null) { 21389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (calendarEmail == null) { 21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 21449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the email address for this attendee 21479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String attendeeEmail = null; 21489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) { 21499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL); 21509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the attendee email does not match the calendar email, then this 21539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // attendee is not the owner of this calendar so we don't update the 21549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // selfAttendeeStatus in the event. 21559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!calendarEmail.equals(attendeeEmail)) { 21569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 21579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int status = Attendees.ATTENDEE_STATUS_NONE; 21619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) { 21629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP); 21639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (rel == Attendees.RELATIONSHIP_ORGANIZER) { 21649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff status = Attendees.ATTENDEE_STATUS_ACCEPTED; 21659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) { 21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS); 21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues values = new ContentValues(); 21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Events.SELF_ATTENDEE_STATUS, status); 2174636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff db.update("Events", values, "_id=?", new String[] {String.valueOf(eventId)}); 21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Updates the instances table when an event is added or updated. 21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param values The new values of the event. 21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param rowId The database row id of the event. 21819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param newEvent true if the event is new. 21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param db The database 21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 21849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void updateInstancesLocked(ContentValues values, 21859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long rowId, 21869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean newEvent, 21879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff SQLiteDatabase db) { 21889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If there are no expanded Instances, then return. 21909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff MetaData.Fields fields = mMetaData.getFieldsLocked(); 21919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (fields.maxInstance == 0) { 21929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 21939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 21949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 21959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long dtstartMillis = values.getAsLong(Events.DTSTART); 21969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtstartMillis == null) { 21979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (newEvent) { 21989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // must be present for a new event. 21999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new RuntimeException("DTSTART missing."); 22009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Config.LOGV) Log.v(TAG, "Missing DTSTART. " 22029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + "No need to update instance."); 22039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 22049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long lastDateMillis = values.getAsLong(Events.LAST_DATE); 22079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 22089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!newEvent) { 22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Want to do this for regular event, recurrence, or exception. 22119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // For recurrence or exception, more deletion may happen below if we 22129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // do an instance expansion. This deletion will suffice if the exception 22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // is moved outside the window, for instance. 2214636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)}); 22159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (isRecurrenceEvent(values)) { 22189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // The recurrence or exception needs to be (re-)expanded if: 22199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // a) Exception or recurrence that falls inside window 22209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean insideWindow = dtstartMillis <= fields.maxInstance && 22219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff (lastDateMillis == null || lastDateMillis >= fields.minInstance); 22229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // b) Exception that affects instance inside window 22239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // These conditions match the query in getEntries 22249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // See getEntries comment for explanation of subtracting 1 week. 22259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean affectsWindow = originalInstanceTime != null && 22269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff originalInstanceTime <= fields.maxInstance && 22279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION; 22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (insideWindow || affectsWindow) { 22299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateRecurrenceInstancesLocked(values, rowId, db); 22309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: an exception creation or update could be optimized by 22329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // updating just the affected instances, instead of regenerating 22339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the recurrence. 22349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long dtendMillis = values.getAsLong(Events.DTEND); 22389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtendMillis == null) { 22399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff dtendMillis = dtstartMillis; 22409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // if the event is in the expanded range, insert 22439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // into the instances table. 22449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: deal with durations. currently, durations are only used in 22459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // recurrences. 22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) { 22489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues instanceValues = new ContentValues(); 22499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff instanceValues.put(Instances.EVENT_ID, rowId); 22509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff instanceValues.put(Instances.BEGIN, dtstartMillis); 22519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff instanceValues.put(Instances.END, dtendMillis); 22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean allDay = false; 22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Integer allDayInteger = values.getAsInteger(Events.ALL_DAY); 22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDayInteger != null) { 22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff allDay = allDayInteger != 0; 22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Update the timezone-dependent fields. 22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time local = new Time(); 22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay) { 22629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff local.timezone = Time.TIMEZONE_UTC; 22639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 22649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff local.timezone = fields.timezone; 22659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues); 22689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDbHelper.instancesInsert(instanceValues); 22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Determines the recurrence entries associated with a particular recurrence. 22749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This set is the base recurrence and any exception. 22759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 22769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Normally the entries are indicated by the sync id of the base recurrence 22779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * (which is the originalEvent in the exceptions). 22789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * However, a complication is that a recurrence may not yet have a sync id. 22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * In that case, the recurrence is specified by the rowId. 22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param recurrenceSyncId The sync id of the base recurrence, or null. 22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param rowId The row id of the base recurrence. 22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @return the relevant entries. 22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) { 22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 22881ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff qb.setTables(CalendarDatabaseHelper.Views.EVENTS); 22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.setProjectionMap(sEventsProjectionMap); 2290636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff String selectionArgs[]; 22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (recurrenceSyncId == null) { 2292636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff String where = "_id =?"; 22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.appendWhere(where); 2294636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = new String[] {String.valueOf(rowId)}; 22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 2296636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff String where = "_sync_id = ? OR originalEvent = ?"; 22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff qb.appendWhere(where); 2298636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId}; 22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.VERBOSE)) { 23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "Retrieving events to expand: " + qb.toString()); 23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2304636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs, 23057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* groupBy */, null /* having */, null /* sortOrder */); 23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Do incremental Instances update of a recurrence or recurrence exception. 23109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 23119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This method does performInstanceExpansion on just the modified recurrence, 23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * to avoid the overhead of recomputing the entire instance table. 23139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param values The new values of the event. 23159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param rowId The database row id of the event. 23169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param db The database 23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void updateRecurrenceInstancesLocked(ContentValues values, 23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long rowId, 23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff SQLiteDatabase db) { 23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff MetaData.Fields fields = mMetaData.getFieldsLocked(); 23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String originalEvent = values.getAsString(Events.ORIGINAL_EVENT); 23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String recurrenceSyncId = null; 23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (originalEvent != null) { 23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff recurrenceSyncId = originalEvent; 23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Get the recurrence's sync id from the database 23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff recurrenceSyncId = DatabaseUtils.stringForQuery(db, "SELECT _sync_id FROM Events" 2329636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff + " WHERE _id=?", new String[] {String.valueOf(rowId)}); 23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // recurrenceSyncId is the _sync_id of the underlying recurrence 23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the recurrence hasn't gone to the server, it will be null. 23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Need to clear out old instances 23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (recurrenceSyncId == null) { 23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Creating updating a recurrence that hasn't gone to the server. 23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Need to delete based on row id 23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String where = "_id IN (SELECT Instances._id as _id" 23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " FROM Instances INNER JOIN Events" 23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " ON (Events._id = Instances.event_id)" 23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " WHERE Events._id =?)"; 23429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.delete("Instances", where, new String[]{"" + rowId}); 23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Creating or modifying a recurrence or exception. 23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Delete instances for recurrence (_sync_id = recurrenceSyncId) 23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // and all exceptions (originalEvent = recurrenceSyncId) 23479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String where = "_id IN (SELECT Instances._id as _id" 23489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " FROM Instances INNER JOIN Events" 23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " ON (Events._id = Instances.event_id)" 23509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " WHERE Events._sync_id =?" 23519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " OR Events.originalEvent =?)"; 23529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.delete("Instances", where, new String[]{recurrenceSyncId, recurrenceSyncId}); 23539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Now do instance expansion 23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId); 23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff performInstanceExpansion(fields.minInstance, fields.maxInstance, fields.timezone, 23599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff entries); 23609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (entries != null) { 23629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff entries.close(); 23639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2366dbd797d59294d72d7ea9226d10128674b634aaadErik // Clear busy bits (is this still needed?) 2367dbd797d59294d72d7ea9226d10128674b634aaadErik mMetaData.writeLocked(fields.timezone, fields.minInstance, fields.maxInstance); 23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 23709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long calculateLastDate(ContentValues values) 23719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throws DateException { 23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Allow updates to some event fields like the title or hasAlarm 23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // without requiring DTSTART. 23749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!values.containsKey(Events.DTSTART)) { 23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE) 23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff || values.containsKey(Events.DURATION) 23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff || values.containsKey(Events.EVENT_TIMEZONE) 23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff || values.containsKey(Events.RDATE) 23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff || values.containsKey(Events.EXRULE) 23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff || values.containsKey(Events.EXDATE)) { 23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new RuntimeException("DTSTART field missing from event"); 23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return -1; 23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long dtstartMillis = values.getAsLong(Events.DTSTART); 23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long lastMillis = -1; 23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Can we use dtend with a repeating event? What does that even 23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // mean? 23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // NOTE: if the repeating event has a dtend, we convert it to a 23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // duration during event processing, so this situation should not 23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // occur. 23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long dtEnd = values.getAsLong(Events.DTEND); 23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtEnd != null) { 23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff lastMillis = dtEnd; 23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 23979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // find out how long it is 23989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Duration duration = new Duration(); 23999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String durationStr = values.getAsString(Events.DURATION); 24009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (durationStr != null) { 24019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff duration.parse(durationStr); 24029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2404f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio RecurrenceSet recur = null; 2405f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio try { 2406f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio recur = new RecurrenceSet(values); 2407f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio } catch (EventRecurrence.InvalidFormatException e) { 2408f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio Log.w(TAG, "Could not parse RRULE recurrence string: " + 2409f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio values.get(Calendar.Events.RRULE), e); 2410f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio return lastMillis; // -1 2411f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio } 24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2413f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio if (null != recur && recur.hasRecurrence()) { 24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the event is repeating, so find the last date it 24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // could appear on 24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String tz = values.getAsString(Events.EVENT_TIMEZONE); 24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (TextUtils.isEmpty(tz)) { 24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // floating timezone 24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff tz = Time.TIMEZONE_UTC; 24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time dtstartLocal = new Time(tz); 24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff dtstartLocal.set(dtstartMillis); 24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff RecurrenceProcessor rp = new RecurrenceProcessor(); 24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff lastMillis = rp.getLastOccurence(dtstartLocal, recur); 24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (lastMillis == -1) { 24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return lastMillis; // -1 24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // the event is not repeating, just use dtstartMillis 24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff lastMillis = dtstartMillis; 24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that was the beginning of the event. this is the end. 24389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff lastMillis = duration.addTo(lastMillis); 24399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return lastMillis; 24419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2443e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff /** 2444e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * Add LAST_DATE to values. 2445e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * @param values the ContentValues (in/out) 2446e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff * @return values on success, null on failure 2447e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff */ 2448e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff private ContentValues updateLastDate(ContentValues values) { 24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long last = calculateLastDate(values); 24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (last != -1) { 24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff values.put(Events.LAST_DATE, last); 24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return values; 24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } catch (DateException e) { 24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // don't add it if there was an error 24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "Could not calculate last date.", e); 24599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return null; 24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void updateEventRawTimesLocked(long eventId, ContentValues values) { 24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentValues rawValues = new ContentValues(); 24659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rawValues.put("event_id", eventId); 24679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String timezone = values.getAsString(Events.EVENT_TIMEZONE); 24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean allDay = false; 24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Integer allDayInteger = values.getAsInteger(Events.ALL_DAY); 24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDayInteger != null) { 24739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff allDay = allDayInteger != 0; 24749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDay || TextUtils.isEmpty(timezone)) { 24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // floating timezone 24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff timezone = Time.TIMEZONE_UTC; 24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(timezone); 24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.allDay = allDay; 24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long dtstartMillis = values.getAsLong(Events.DTSTART); 24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtstartMillis != null) { 24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(dtstartMillis); 24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rawValues.put("dtstart2445", time.format2445()); 24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long dtendMillis = values.getAsLong(Events.DTEND); 24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (dtendMillis != null) { 24919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(dtendMillis); 24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rawValues.put("dtend2445", time.format2445()); 24939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (originalInstanceMillis != null) { 24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // This is a recurrence exception so we need to get the all-day 24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // status of the original recurring event in order to format the 24999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // date correctly. 25009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY); 25019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (allDayInteger != null) { 25029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.allDay = allDayInteger != 0; 25039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(originalInstanceMillis); 25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rawValues.put("originalInstanceTime2445", time.format2445()); 25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Long lastDateMillis = values.getAsLong(Events.LAST_DATE); 25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (lastDateMillis != null) { 25109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.allDay = allDay; 25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(lastDateMillis); 25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff rawValues.put("lastDate2445", time.format2445()); 25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDbHelper.eventsRawTimesReplace(rawValues); 25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 25189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 25199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) { 2520ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 25219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "deleteInTransaction: " + uri); 25229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final boolean callerIsSyncAdapter = 25249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false); 25259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final int match = sUriMatcher.match(uri); 25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff switch (match) { 25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE: 25289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs); 25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE_ID: 2531dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff String selectionWithId = (BaseColumns._ID + "=?") 25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + (selection == null ? "" : " AND (" + selection + ")"); 25339323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff // Prepend id to selectionArgs 2534dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff selectionArgs = insertSelectionArg(selectionArgs, 2535dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff String.valueOf(ContentUris.parseId(uri))); 2536dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff return mDbHelper.getSyncState().delete(mDb, selectionWithId, 2537dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff selectionArgs); 25389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 25391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff case EVENTS: 25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 25417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff int result = 0; 25421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff selection = appendAccountToSelection(uri, selection); 25437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 25441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff // Query this event to get the ids to delete. 25451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff Cursor cursor = mDb.query("Events", ID_ONLY_PROJECTION, 25461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff selection, selectionArgs, null /* groupBy */, 25477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* having */, null /* sortOrder */); 25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 25491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff while (cursor.moveToNext()) { 25501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff long id = cursor.getLong(0); 255110b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */); 25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 255310b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio scheduleNextAlarm(false /* do not remove alarms */); 2554dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(callerIsSyncAdapter); 25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor = null; 25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return result; 25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25611ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff case EVENTS_ID: 25621ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff { 25631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff long id = ContentUris.parseId(uri); 25641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff if (selection != null) { 25651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff throw new UnsupportedOperationException("CalendarProvider2 " 25661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff + "doesn't support selection based deletion for type " 25671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff + match); 25681ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 256910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */); 25701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 25719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case ATTENDEES: 25729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 25737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 25747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return mDb.delete("Attendees", selection, selectionArgs); 25757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 25767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return deleteFromTable("Attendees", uri, selection, selectionArgs); 25777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 25789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case ATTENDEES_ID: 25809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 25812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 25822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 25832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 25847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 25857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = ContentUris.parseId(uri); 2586636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.delete("Attendees", "_id=?", new String[] {String.valueOf(id)}); 25877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 25882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return deleteFromTable("Attendees", uri, null /* selection */, 25892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 25907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 25919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 25929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS: 25939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 25947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 25957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return mDb.delete("Reminders", selection, selectionArgs); 25967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 25977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return deleteFromTable("Reminders", uri, selection, selectionArgs); 25987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case REMINDERS_ID: 26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 26022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 26032fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 26042fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 26067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = ContentUris.parseId(uri); 2607636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.delete("Reminders", "_id=?", new String[] {String.valueOf(id)}); 26087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 26092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return deleteFromTable("Reminders", uri, null /* selection */, 26102fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 26112fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26122fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case EXTENDED_PROPERTIES: 26142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff { 26152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (callerIsSyncAdapter) { 26162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return mDb.delete("ExtendedProperties", selection, selectionArgs); 26172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } else { 26182fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return deleteFromTable("ExtendedProperties", uri, selection, selectionArgs); 26192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26202fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26212fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case EXTENDED_PROPERTIES_ID: 26222fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff { 26232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 26242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 26252fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (callerIsSyncAdapter) { 26272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff long id = ContentUris.parseId(uri); 2628636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.delete("ExtendedProperties", "_id=?", 2629636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff new String[] {String.valueOf(id)}); 26302fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } else { 26312fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return deleteFromTable("ExtendedProperties", uri, null /* selection */, 26322fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 26337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS: 26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 26377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 26387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return mDb.delete("CalendarAlerts", selection, selectionArgs); 26397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 26407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return deleteFromTable("CalendarAlerts", uri, selection, selectionArgs); 26417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDAR_ALERTS_ID: 26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 26452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 26462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 26472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 26482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // Note: dirty bit is not set for Alerts because it is not synced. 26492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // It is generated from Reminders, which is synced. 26509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long id = ContentUris.parseId(uri); 2651636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.delete("CalendarAlerts", "_id=?", new String[] {String.valueOf(id)}); 26529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case DELETED_EVENTS: 26547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff throw new UnsupportedOperationException("Cannot delete that URL: " + uri); 26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS_ID: 26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff StringBuilder selectionSb = new StringBuilder("_id="); 26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(uri.getPathSegments().get(1)); 26589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!TextUtils.isEmpty(selection)) { 26599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(" AND ("); 26609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(selection); 26619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(')'); 26629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selection = selectionSb.toString(); 26649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // fall through to CALENDARS for the actual delete 26659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS: 2666595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff selection = appendAccountToSelection(uri, selection); 26677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return deleteMatchingCalendars(selection); // TODO: handle in sync adapter 26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES: 26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case INSTANCES_BY_DAY: 26706db535b458146a279bebd4a51d56c1bdfc204528Erik case EVENT_DAYS: 26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new UnsupportedOperationException("Cannot delete that URL"); 26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff default: 26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Unknown URL " + uri); 26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 267710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) { 26781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff int result = 0; 2679192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank String selectionArgs[] = new String[] {String.valueOf(id)}; 26801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff 26811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff // Query this event to get the fields needed for deleting. 26821ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff Cursor cursor = mDb.query("Events", EVENTS_PROJECTION, 2683192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank "_id=?", selectionArgs, 2684636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff null /* groupBy */, 26851ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff null /* having */, null /* sortOrder */); 26861ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff try { 26871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff if (cursor.moveToNext()) { 26881ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff result = 1; 26891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX); 2690370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik String rRule = cursor.getString(EVENTS_RRULE_INDEX); 2691370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik boolean emptyRRule = TextUtils.isEmpty(rRule); 269248f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio boolean emptySyncId = TextUtils.isEmpty(syncId); 2693370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik if (!emptySyncId && !emptyRRule) { 2694370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik // Delete exceptions to this event as well. 2695370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik mDb.delete("Events", "originalEvent=?", new String[] {syncId}); 26961ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 26971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff 26981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff // If this was a recurring event or a recurrence 26991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff // exception, then force a recalculation of the 27001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff // instances. 27011ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff String rrule = cursor.getString(EVENTS_RRULE_INDEX); 27021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff String rdate = cursor.getString(EVENTS_RDATE_INDEX); 27031ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX); 27041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate) 27051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff || !TextUtils.isEmpty(origEvent)) { 27061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff mMetaData.clearInstanceRange(); 27071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 27081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff 270948f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio // we clean the Events and Attendees table if the caller is CalendarSyncAdapter 271048f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio // or if the event is local (no syncId) 271148f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio if (callerIsSyncAdapter || emptySyncId) { 2712192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank mDb.delete("Events", "_id=?", selectionArgs); 27131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } else { 27141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff ContentValues values = new ContentValues(); 27151b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio values.put(Events.DELETED, 1); 27161ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff values.put(Events._SYNC_DIRTY, 1); 2717192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank mDb.update("Events", values, "_id=?", selectionArgs); 271802494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio 271943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // Delete associated data; attendees, however, are deleted with the actual event 272043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // so that the sync adapter is able to notify attendees of the cancellation. 272102494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio mDb.delete("Instances", "event_id=?", selectionArgs); 272202494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio mDb.delete("EventsRawTimes", "event_id=?", selectionArgs); 272302494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio mDb.delete("Reminders", "event_id=?", selectionArgs); 272402494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio mDb.delete("CalendarAlerts", "event_id=?", selectionArgs); 272502494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio mDb.delete("ExtendedProperties", "event_id=?", selectionArgs); 27261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 27271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 27281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } finally { 27291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff cursor.close(); 27301ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff cursor = null; 27311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 27328f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 273310b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio if (!isBatch) { 273410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio scheduleNextAlarm(false /* do not remove alarms */); 2735dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(callerIsSyncAdapter); 273610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio } 27371ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff return result; 27381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff } 27391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff 27407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff /** 27417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * Delete rows from a table and mark corresponding events as dirty. 27427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param table The table to delete from 27437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param uri The URI specifying the rows 27447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param selection for the query 27457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param selectionArgs for the query 27467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff */ 27477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) { 27487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // Note that the query will return data according to the access restrictions, 27497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // so we don't need to worry about deleting data we don't have permission to read. 27507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null); 27517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff ContentValues values = new ContentValues(); 27527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff values.put(Events._SYNC_DIRTY, "1"); 27537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff int count = 0; 27547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff try { 27557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff while(c.moveToNext()) { 27567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = c.getLong(ID_INDEX); 27577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long event_id = c.getLong(EVENT_ID_INDEX); 2758636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff mDb.delete(table, "_id=?", new String[] {String.valueOf(id)}); 2759636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff mDb.update("Events", values, "_id=?", new String[] {String.valueOf(event_id)}); 27607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff count++; 27617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } finally { 27637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff c.close(); 27647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return count; 27667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 27687e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff /** 27697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * Update rows in a table and mark corresponding events as dirty. 27707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param table The table to delete from 27717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param values The values to update 27727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param uri The URI specifying the rows 27737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param selection for the query 27747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * @param selectionArgs for the query 27757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff */ 27767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff private int updateInTable(String table, ContentValues values, Uri uri, String selection, 27777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff String[] selectionArgs) { 27787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // Note that the query will return data according to the access restrictions, 27797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // so we don't need to worry about deleting data we don't have permission to read. 27807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null); 27817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff ContentValues dirtyValues = new ContentValues(); 27827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff dirtyValues.put(Events._SYNC_DIRTY, "1"); 27837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff int count = 0; 27847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff try { 27857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff while(c.moveToNext()) { 27867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = c.getLong(ID_INDEX); 27877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long event_id = c.getLong(EVENT_ID_INDEX); 2788636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff mDb.update(table, values, "_id=?", new String[] {String.valueOf(id)}); 2789636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff mDb.update("Events", dirtyValues, "_id=?", new String[] {String.valueOf(event_id)}); 27907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff count++; 27917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } finally { 27937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff c.close(); 27947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return count; 27967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 27977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 27989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private int deleteMatchingCalendars(String where) { 27999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // query to find all the calendars that match, for each 28009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // - delete calendar subscription 28019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // - delete calendar 28029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff Cursor c = mDb.query("Calendars", sCalendarsIdProjection, where, 28047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* selectionArgs */, null /* groupBy */, 28057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff null /* having */, null /* sortOrder */); 28069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (c == null) { 28079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return 0; 28089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 28109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff while (c.moveToNext()) { 28119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long id = c.getLong(CALENDARS_INDEX_ID); 28129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff modifyCalendarSubscription(id, false /* not selected */); 28139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff c.close(); 28169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28172cc859cab85391a240b9c3f28c935d919c8ceb8cKen Shirriff return mDb.delete("Calendars", where, null /* whereArgs */); 28189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: call calculateLastDate()! 28219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff @Override 28229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff protected int updateInTransaction(Uri uri, ContentValues values, String selection, 28239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String[] selectionArgs) { 2824ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio if (Log.isLoggable(TAG, Log.VERBOSE)) { 28259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.v(TAG, "updateInTransaction: " + uri); 28269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int count = 0; 28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final int match = sUriMatcher.match(uri); 28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final boolean callerIsSyncAdapter = 28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false); 28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: remove this restriction 283643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS 283743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio && match != EVENTS && match != CALENDARS) { 28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException( 28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff "WHERE based updates not supported"); 28409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff switch (match) { 28429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE: 28439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return mDbHelper.getSyncState().update(mDb, values, 28449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff appendAccountToSelection(uri, selection), selectionArgs); 28459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case SYNCSTATE_ID: { 28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selection = appendAccountToSelection(uri, selection); 2848dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff String selectionWithId = (BaseColumns._ID + "=?") 2849dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff + (selection == null ? "" : " AND (" + selection + ")"); 28509323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff // Prepend id to selectionArgs 2851dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff selectionArgs = insertSelectionArg(selectionArgs, 2852dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff String.valueOf(ContentUris.parseId(uri))); 2853dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs); 28549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 285643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio case CALENDARS: 28579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case CALENDARS_ID: 28589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 285943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio long id; 286043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio if (match == CALENDARS_ID) { 286143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio if (selection != null) { 286243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio throw new UnsupportedOperationException("Selection not permitted for " 286343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio + uri); 286443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio } 286543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio id = ContentUris.parseId(uri); 286643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio } else { 286743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // TODO: for supporting other sync adapters, we will need to 286843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // be able to deal with the following cases: 286943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // 1) selection to "_id=?" and pass in a selectionArgs 287043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // 2) selection to "_id IN (1, 2, 3)" 287143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // 3) selection to "delete=0 AND _id=1" 287243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio if (selection != null && selection.startsWith("_id=")) { 287343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // The ContentProviderOperation generates an _id=n string instead of 287443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // adding the id to the URL, so parse that out here. 287543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio id = Long.parseLong(selection.substring(4)); 287643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio } else { 287743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio return mDb.update("Calendars", values, selection, selectionArgs); 287843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio } 287943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio } 288043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio if (!callerIsSyncAdapter) { 288143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio values.put(Calendars._SYNC_DIRTY, 1); 28822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 28839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS); 28849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (syncEvents != null) { 28859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff modifyCalendarSubscription(id, syncEvents == 1); 28869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2888636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff int result = mDb.update("Calendars", values, "_id=?", 2889636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff new String[] {String.valueOf(id)}); 28909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 28913ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang if (result > 0) { 28923ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang // update the widget 2893dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(callerIsSyncAdapter); 28943ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang } 28953ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang 28969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return result; 28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 28987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff case EVENTS: 28999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff case EVENTS_ID: 29009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff { 29017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = 0; 29027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (match == EVENTS_ID) { 29037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff id = ContentUris.parseId(uri); 2904a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff } else if (callerIsSyncAdapter) { 290543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // TODO: same remark as for CALENDARS/CALENDARS_ID case as this is not 290643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio // sufficient to deal with all the "_id" case in selection 2907a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff if (selection != null && selection.startsWith("_id=")) { 29087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // The ContentProviderOperation generates an _id=n string instead of 29097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // adding the id to the URL, so parse that out here. 29107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff id = Long.parseLong(selection.substring(4)); 2911a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff } else { 2912a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff // Sync adapter Events operation affects just Events table, not associated 2913a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff // tables. 2914646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (fixAllDayTime(uri, values)) { 2915646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Log.w(TAG, "updateInTransaction: Caller is sync adapter. " + 2916646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik "allDay is true but sec, min, hour were not 0."); 2917646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 2918a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff return mDb.update("Events", values, selection, selectionArgs); 2919a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff } 29207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 2921a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff throw new IllegalArgumentException("Unknown URL " + uri); 29227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 29237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (!callerIsSyncAdapter) { 29247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff values.put(Events._SYNC_DIRTY, 1); 29257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Disallow updating the attendee status in the Events 29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // table. In the future, we could support this but we 29289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // would have to query and update the attendees table 29299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // to keep the values consistent. 29309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) { 29319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Updating " 29329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + Events.SELF_ATTENDEE_STATUS 29339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " in Events table is not allowed."); 29349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 29367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff // TODO: should we allow this? 29377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (values.containsKey(Events.HTML_URI) && !callerIsSyncAdapter) { 29389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Updating " 29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + Events.HTML_URI 29409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " in Events table is not allowed."); 29419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 2942e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff ContentValues updatedValues = new ContentValues(values); 2943e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff // TODO: should extend validateEventData to work with updates and call it here 2944e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff updatedValues = updateLastDate(updatedValues); 29459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (updatedValues == null) { 29469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "Could not update event."); 29479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return 0; 29489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 2949646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik // Make sure we pass in a uri with the id appended to fixAllDayTime 2950646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Uri allDayUri; 2951646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (uri.getPathSegments().size() == 1) { 2952646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik allDayUri = ContentUris.withAppendedId(uri, id); 2953646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } else { 2954646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik allDayUri = uri; 2955646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 2956646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik if (fixAllDayTime(allDayUri, updatedValues)) { 2957646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik Log.w(TAG, "updateInTransaction: " + 2958646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik "allDay is true but sec, min, hour were not 0."); 2959646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik } 29609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 2961636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff int result = mDb.update("Events", updatedValues, "_id=?", 2962636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff new String[] {String.valueOf(id)}); 29639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (result > 0) { 29649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateEventRawTimesLocked(id, updatedValues); 29659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb); 29669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 29679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (values.containsKey(Events.DTSTART)) { 29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // The start time of the event changed, so run the 29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // event alarm scheduler. 29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 29719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "updateInternal() changing event"); 29729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 29749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29753ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang 2976dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(id, callerIsSyncAdapter); 29779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29783ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang 29799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return result; 29809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case ATTENDEES_ID: { 29822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 29832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 29842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 29859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Copy the attendee status value to the Events table. 29869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff updateEventAttendeeStatus(mDb, values); 29879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 29887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 29897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = ContentUris.parseId(uri); 2990636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.update("Attendees", values, "_id=?", 299183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff new String[] {String.valueOf(id)}); 29927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 29932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return updateInTable("Attendees", values, uri, null /* selection */, 29942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 29957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 29969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 29972fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case CALENDAR_ALERTS_ID: { 29982fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 29992fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 30002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 30012fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // Note: dirty bit is not set for Alerts because it is not synced. 30022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // It is generated from Reminders, which is synced. 30039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long id = ContentUris.parseId(uri); 3004636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.update("CalendarAlerts", values, "_id=?", 3005636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff new String[] {String.valueOf(id)}); 30069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30072fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case CALENDAR_ALERTS: { 30082fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // Note: dirty bit is not set for Alerts because it is not synced. 30092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff // It is generated from Reminders, which is synced. 30109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return mDb.update("CalendarAlerts", values, selection, selectionArgs); 30119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30122fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case REMINDERS_ID: { 30132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 30142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 30152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 30167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 30177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = ContentUris.parseId(uri); 3018636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff count = mDb.update("Reminders", values, "_id=?", 301983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff new String[] {String.valueOf(id)}); 30207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 30212fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff count = updateInTable("Reminders", values, uri, null /* selection */, 30222fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 30237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 30247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff 30259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Reschedule the event alarms because the 30269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // "minutes" field may have changed. 30279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 30289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "updateInternal() changing reminder"); 30299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarm(false /* do not remove alarms */); 30317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff return count; 30329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30332fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff case EXTENDED_PROPERTIES_ID: { 30342fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff if (selection != null) { 30352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff throw new UnsupportedOperationException("Selection not permitted for " + uri); 30362fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff } 30377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff if (callerIsSyncAdapter) { 30387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff long id = ContentUris.parseId(uri); 3039636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return mDb.update("ExtendedProperties", values, "_id=?", 3040636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff new String[] {String.valueOf(id)}); 30417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } else { 30422fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff return updateInTable("ExtendedProperties", values, uri, null /* selection */, 30432fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff null /* selectionArgs */); 30447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff } 30459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 304683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // TODO: replace the SCHEDULE_ALARM private URIs with a 304783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff // service 304883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff case SCHEDULE_ALARM: { 304983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff scheduleNextAlarm(false); 305083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff return 0; 305183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff } 305283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff case SCHEDULE_ALARM_REMOVE: { 305383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff scheduleNextAlarm(true); 305483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff return 0; 305583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff } 30569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff default: 30589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff throw new IllegalArgumentException("Unknown URL " + uri); 30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3062595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) { 3063595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME); 3064595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE); 3065595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (!TextUtils.isEmpty(accountName)) { 3066595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff qb.appendWhere(Calendar.Calendars._SYNC_ACCOUNT + "=" 3067595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff + DatabaseUtils.sqlEscapeString(accountName) + " AND " 3068595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=" 3069595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff + DatabaseUtils.sqlEscapeString(accountType)); 3070595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } else { 3071595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff qb.appendWhere("1"); // I.e. always true 3072595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3073595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3074595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 30759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private String appendAccountToSelection(Uri uri, String selection) { 3076595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME); 3077595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE); 30789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!TextUtils.isEmpty(accountName)) { 30799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff StringBuilder selectionSb = new StringBuilder(Calendar.Calendars._SYNC_ACCOUNT + "=" 30809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + DatabaseUtils.sqlEscapeString(accountName) + " AND " 30819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=" 30829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + DatabaseUtils.sqlEscapeString(accountType)); 30839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!TextUtils.isEmpty(selection)) { 30849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(" AND ("); 30859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(selection); 30869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff selectionSb.append(')'); 30879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return selectionSb.toString(); 30899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 30909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return selection; 30919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 30939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 30949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void modifyCalendarSubscription(long id, boolean syncEvents) { 30959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // get the account, url, and current selected state 30969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // for this calendar. 30979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id), 30989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE, 30991b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio Calendars.SYNC1, Calendars.SYNC_EVENTS}, 31009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selection */, 31019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* selectionArgs */, 31029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff null /* sort */); 31039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Account account = null; 31059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String calendarUrl = null; 31069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff boolean oldSyncEvents = false; 3107ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff if (cursor != null) { 31089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 3109ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff if (cursor.moveToFirst()) { 3110ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff final String accountName = cursor.getString(0); 3111ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff final String accountType = cursor.getString(1); 3112ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff account = new Account(accountName, accountType); 3113ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff calendarUrl = cursor.getString(2); 3114ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff oldSyncEvents = (cursor.getInt(3) != 0); 3115ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff } 31169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 31179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 31189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31219535627bf6295cd94447beb83e1aac41f50c3600Erik if (account == null) { 31229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // should not happen? 31239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.w(TAG, "Cannot update subscription because account " 31249535627bf6295cd94447beb83e1aac41f50c3600Erik + "is empty -- should not happen."); 31259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 31269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31289535627bf6295cd94447beb83e1aac41f50c3600Erik if (TextUtils.isEmpty(calendarUrl)) { 31299535627bf6295cd94447beb83e1aac41f50c3600Erik // Passing in a null Url will cause it to not add any extras 31309535627bf6295cd94447beb83e1aac41f50c3600Erik // Should only happen for non-google calendars. 31319535627bf6295cd94447beb83e1aac41f50c3600Erik calendarUrl = null; 31329535627bf6295cd94447beb83e1aac41f50c3600Erik } 31339535627bf6295cd94447beb83e1aac41f50c3600Erik 31349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (oldSyncEvents == syncEvents) { 31359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // nothing to do 31369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 31379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If the calendar is not selected for syncing, then don't download 31409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // events. 31419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDbHelper.scheduleSync(account, !syncEvents, calendarUrl); 31429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // TODO: is this needed 31459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// @Override 31469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// public void onSyncStop(SyncContext context, boolean success) { 31479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// super.onSyncStop(context, success); 31489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// if (Log.isLoggable(TAG, Log.DEBUG)) { 31499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// Log.d(TAG, "onSyncStop() success: " + success); 31509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// } 31519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// scheduleNextAlarm(false /* do not remove alarms */); 31529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// triggerAppWidgetUpdate(-1); 31539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff// } 31549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /* Retrieve and cache the alarm manager */ 31569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private AlarmManager getAlarmManager() { 31579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff synchronized(mAlarmLock) { 31589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (mAlarmManager == null) { 31599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Context context = getContext(); 31609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (context == null) { 31619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "getAlarmManager() cannot get Context"); 31629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return null; 31639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Object service = context.getSystemService(Context.ALARM_SERVICE); 31659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mAlarmManager = (AlarmManager) service; 31669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return mAlarmManager; 31689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff void scheduleNextAlarmCheck(long triggerTime) { 31729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff AlarmManager manager = getAlarmManager(); 31739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (manager == null) { 31749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "scheduleNextAlarmCheck() cannot get AlarmManager"); 31759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 31769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Context context = getContext(); 31789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Intent intent = new Intent(CalendarReceiver.SCHEDULE); 31799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff intent.setClass(context, CalendarReceiver.class); 31809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff PendingIntent pending = PendingIntent.getBroadcast(context, 31819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 0, intent, PendingIntent.FLAG_NO_CREATE); 31829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (pending != null) { 31839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Cancel any previous alarms that do the same thing. 31849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff manager.cancel(pending); 31859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff pending = PendingIntent.getBroadcast(context, 31879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 31889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 31909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 31919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(triggerTime); 31929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String timeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 31939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr); 31949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff manager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending); 31979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 31989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 31999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /* 32009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This method runs the alarm scheduler in a background thread. 32019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 32029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff void scheduleNextAlarm(boolean removeAlarms) { 32039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Thread thread = new AlarmScheduler(removeAlarms); 32049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff thread.start(); 32059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 32089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This method runs in a background thread and schedules an alarm for 32099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * the next calendar event, if necessary. 32109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 32119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void runScheduleNextAlarm(boolean removeAlarms) { 32129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final SQLiteDatabase db = mDbHelper.getWritableDatabase(); 32139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.beginTransaction(); 32149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 32159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (removeAlarms) { 32169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff removeScheduledAlarmsLocked(db); 32179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff scheduleNextAlarmLocked(db); 32199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.setTransactionSuccessful(); 32209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 32219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.endTransaction(); 32229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 32269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * This method looks at the 24-hour window from now for any events that it 32279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * needs to schedule. This method runs within a database transaction. It 32289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * also runs in a background thread. 32299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 32309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * The CalendarProvider2 keeps track of which alarms it has already scheduled 32319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * to avoid scheduling them more than once and for debugging problems with 32329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * alarms. It stores this knowledge in a database table called CalendarAlerts 32339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * which persists across reboots. But the actual alarm list is in memory 32349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * and disappears if the phone loses power. To avoid missing an alarm, we 32359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * clear the entries in the CalendarAlerts table when we start up the 32369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * CalendarProvider2. 32379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 32389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Scheduling an alarm multiple times is not tragic -- we filter out the 32399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * extra ones when we receive them. But we still need to keep track of the 32409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * scheduled alarms. The main reason is that we need to prevent multiple 32419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * notifications for the same alarm (on the receive side) in case we 32429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * accidentally schedule the same alarm multiple times. We don't have 32439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * visibility into the system's alarm list so we can never know for sure if 32449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * we have already scheduled an alarm and it's better to err on scheduling 32459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * an alarm twice rather than missing an alarm. Another reason we keep 32469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * track of scheduled alarms in a database table is that it makes it easy to 32479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * run an SQL query to find the next reminder that we haven't scheduled. 32489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 32499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * @param db the database 32509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 32519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void scheduleNextAlarmLocked(SQLiteDatabase db) { 32529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff AlarmManager alarmManager = getAlarmManager(); 32539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (alarmManager == null) { 32549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "Failed to find the AlarmManager. Could not schedule the next alarm!"); 32559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return; 32569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final long currentMillis = System.currentTimeMillis(); 32599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final long start = currentMillis - SCHEDULE_ALARM_SLACK; 32609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff final long end = start + (24 * 60 * 60 * 1000); 32619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ContentResolver cr = getContext().getContentResolver(); 32629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 32639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 32649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(start); 32659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 32669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "runScheduleNextAlarm() start search: " + startTimeStr); 32679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32698f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff // Delete rows in CalendarAlert where the corresponding Instance or 32708f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff // Reminder no longer exist. 32718f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff // Also clear old alarms but keep alarms around for a while to prevent 32729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // multiple alerts for the same reminder. The "clearUpToTime' 32739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // should be further in the past than the point in time where 32749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // we start searching for events (the "start" variable defined above). 32758f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff String selectArg[] = new String[] { 32768f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff Long.toString(currentMillis - CLEAR_OLD_ALARM_THRESHOLD) 32778f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff }; 32788f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 32798f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff int rowsDeleted = 32808f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff db.delete(CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg); 32819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff long nextAlarmTime = end; 32838f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(cr, currentMillis); 32848f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) { 32858f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff nextAlarmTime = tmpAlarmTime; 32869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 32879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 32889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Extract events from the database sorted by alarm time. The 32899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // alarm times are computed from Instances.begin (whose units 32909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // are milliseconds) and Reminders.minutes (whose units are 32919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // minutes). 32929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // 32939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Also, ignore events whose end time is already in the past. 32949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Also, ignore events alarms that we have already scheduled. 32959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // 32969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Note 1: we can add support for the case where Reminders.minutes 32979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // equals -1 to mean use Calendars.minutes by adding a UNION for 32989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // that case where the two halves restrict the WHERE clause on 32999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Reminders.minutes != -1 and Reminders.minutes = 1, respectively. 33009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // 33019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Note 2: we have to name "myAlarmTime" different from the 33029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // "alarmTime" column in CalendarAlerts because otherwise the 33039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // query won't find multiple alarms for the same event. 3304156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff // 3305156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff // The CAST is needed in the query because otherwise the expression 3306156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff // will be untyped and sqlite3's manifest typing will not convert the 3307156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff // string query parameter to an int in myAlarmtime>=?, so the comparison 3308156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff // will fail. This could be simplified if bug 2464440 is resolved. 33099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String query = "SELECT begin-(minutes*60000) AS myAlarmTime," 33109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " Instances.event_id AS eventId, begin, end," 33119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " title, allDay, method, minutes" 33129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " FROM Instances INNER JOIN Events" 33139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " ON (Events._id = Instances.event_id)" 33149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " INNER JOIN Reminders" 33159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " ON (Instances.event_id = Reminders.event_id)" 33169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " WHERE method=" + Reminders.METHOD_ALERT 3317156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff + " AND myAlarmTime>=CAST(? AS INT)" 3318156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff + " AND myAlarmTime<=CAST(? AS INT)" 3319156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff + " AND end>=?" 33209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " AND 0=(SELECT count(*) from CalendarAlerts CA" 33219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " where CA.event_id=Instances.event_id AND CA.begin=Instances.begin" 33229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " AND CA.alarmTime=myAlarmTime)" 33239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff + " ORDER BY myAlarmTime,begin,title"; 3324156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff String queryParams[] = new String[] {String.valueOf(start), String.valueOf(nextAlarmTime), 3325156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff String.valueOf(currentMillis)}; 33269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff acquireInstanceRangeLocked(start, end, false /* don't use minimum expansion windows */); 33289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Cursor cursor = null; 33299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 3330156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff cursor = db.rawQuery(query, queryParams); 33319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33328f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int beginIndex = cursor.getColumnIndex(Instances.BEGIN); 33338f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int endIndex = cursor.getColumnIndex(Instances.END); 33348f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int eventIdIndex = cursor.getColumnIndex("eventId"); 33358f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime"); 33368f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES); 33379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 33399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 33409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(nextAlarmTime); 33419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 33428f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff Log.d(TAG, "cursor results: " + cursor.getCount() + " nextAlarmTime: " 33438f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + alarmTimeStr); 33449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 33459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff while (cursor.moveToNext()) { 33479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Schedule all alarms whose alarm time is as early as any 33489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // scheduled alarm. For example, if the earliest alarm is at 33499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // 1pm, then we will schedule all alarms that occur at 1pm 33509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // but no alarms that occur later than 1pm. 33519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Actually, we allow alarms up to a minute later to also 33529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // be scheduled so that we don't have to check immediately 33539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // again after an event alarm goes off. 33548f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final long alarmTime = cursor.getLong(alarmTimeIndex); 33558f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final long eventId = cursor.getLong(eventIdIndex); 33568f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final int minutes = cursor.getInt(minutesIndex); 33578f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final long startTime = cursor.getLong(beginIndex); 33588f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff final long endTime = cursor.getLong(endIndex); 33599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 33619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Time time = new Time(); 33629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(alarmTime); 33639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String schedTime = time.format(" %a, %b %d, %Y %I:%M%P"); 33649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff time.set(startTime); 33659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P"); 33668f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 33678f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff Log.d(TAG, " looking at id: " + eventId + " " + startTime + startTimeStr 33688f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff + " alarm: " + alarmTime + schedTime); 33699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 33709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (alarmTime < nextAlarmTime) { 33729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff nextAlarmTime = alarmTime; 33739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else if (alarmTime > 33741edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) { 33759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // This event alarm (and all later ones) will be scheduled 33769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // later. 33778f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 33788f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff Log.d(TAG, "This event alarm (and all later ones) will be scheduled later"); 33798f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff } 33809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff break; 33819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 33829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Avoid an SQLiteContraintException by checking if this alarm 33849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // already exists in the table. 33859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (CalendarAlerts.alarmExists(cr, eventId, startTime, alarmTime)) { 33869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 33879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff int titleIndex = cursor.getColumnIndex(Events.TITLE); 33889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String title = cursor.getString(titleIndex); 33899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, " alarm exists for id: " + eventId + " " + title); 33909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 33919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 33929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 33939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 33949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Insert this alarm into the CalendarAlerts table 33959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Uri uri = CalendarAlerts.insert(cr, eventId, startTime, 33969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff endTime, alarmTime, minutes); 33979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (uri == null) { 33989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.e(TAG, "runScheduleNextAlarm() insert into CalendarAlerts table failed"); 33999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff continue; 34009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 34028f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff CalendarAlerts.scheduleAlarm(getContext(), alarmManager, alarmTime); 34039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 34059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (cursor != null) { 34069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff cursor.close(); 34079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 34108f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff // Refresh notification bar 34118f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff if (rowsDeleted > 0) { 34128f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff CalendarAlerts.scheduleAlarm(getContext(), alarmManager, currentMillis); 34138f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff } 34148f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff 34159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // If we scheduled an event alarm, then schedule the next alarm check 34169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // for one minute past that alarm. Otherwise, if there were no 34179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // event alarms scheduled, then check again in 24 hours. If a new 34189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // event is inserted before the next alarm check, then this method 34199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // will be run again when the new event is inserted. 34209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (nextAlarmTime != Long.MAX_VALUE) { 34211edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS); 34229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } else { 34231edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS); 34249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 34279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 34289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Removes the entries in the CalendarAlerts table for alarms that we have 34299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * scheduled but that have not fired yet. We do this to ensure that we 34309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * don't miss an alarm. The CalendarAlerts table keeps track of the 34319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * alarms that we have scheduled but the actual alarm list is in memory 34329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * and will be cleared if the phone reboots. 34339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 34349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * We don't need to remove entries that have already fired, and in fact 34359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * we should not remove them because we need to display the notifications 34369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * until the user dismisses them. 34379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * 34389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * We could remove entries that have fired and been dismissed, but we leave 34399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * them around for a while because it makes it easier to debug problems. 34409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Entries that are old enough will be cleaned up later when we schedule 34419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * new alarms. 34429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 34439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private void removeScheduledAlarmsLocked(SQLiteDatabase db) { 34449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (Log.isLoggable(TAG, Log.DEBUG)) { 34459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "removing scheduled alarms"); 34469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff db.delete(CalendarAlerts.TABLE_NAME, 34489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED, null /* whereArgs */); 34499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 34509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3451a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang /** 3452a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent. 3453a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * This also provides a timeout, so any calls to this method will be batched 3454a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class. 3455dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang * 3456dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang * @param whether or not the update is being triggered by a sync 3457a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang */ 3458dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang private void sendUpdateNotification(boolean callerIsSyncAdapter) { 3459dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // We use -1 to represent an update to all events 3460dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(-1, callerIsSyncAdapter); 3461a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } 3462a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang 3463a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang /** 3464a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent. 3465a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * This also provides a timeout, so any calls to this method will be batched 3466a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class. The 3467a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * actual sending of the intent is done in 3468a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * {@link #doSendUpdateNotification()}. 3469a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * 3470a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * TODO add support for eventId 3471a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * 3472a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * @param the ID of the event that changed, or -1 for no specific event 3473dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang * @param whether or not the update is being triggered by a sync 3474a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang */ 3475dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang private void sendUpdateNotification(long eventId, 3476dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang boolean callerIsSyncAdapter) { 3477a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang // Are there any pending broadcast requests? 3478a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) { 3479a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang // Delete any pending requests, before requeuing a fresh one 3480a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG); 3481a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } else { 3482dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // Because the handler does not guarantee message delivery in 3483dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // the case that the provider is killed, we need to make sure 3484dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // that the provider stays alive long enough to deliver the 3485dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // notification. This empty service is sufficient to "wedge" the 3486dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // process until we stop it here. 3487dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang mContext.startService(new Intent(mContext, EmptyService.class)); 3488dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang } 3489dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // We use a much longer delay for sync-related updates, to prevent any 3490dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // receivers from slowing down the sync 3491dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang long delay = callerIsSyncAdapter ? 3492dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS : 3493dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang UPDATE_BROADCAST_TIMEOUT_MILLIS; 3494dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // Despite the fact that we actually only ever use one message at a time 3495dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // for now, it is really important to call obtainMessage() to get a 3496dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // clean instance. This avoids potentially infinite loops resulting 3497dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // adding the same instance to the message queue twice, since the 3498dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang // message queue implements its linked list using a field from Message. 3499a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG); 3500dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang mBroadcastHandler.sendMessageDelayed(msg, delay); 3501a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } 3502a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang 3503a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang /** 3504a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * This method should not ever be called directly, to prevent sending too 3505a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * many potentially expensive broadcasts. Instead, call 3506a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * {@link #sendUpdateNotification()} instead. 3507a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * 3508a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang * @see #sendUpdateNotification() 3509a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang */ 3510a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang private void doSendUpdateNotification() { 3511a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED, 3512dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang Calendar.CONTENT_URI); 3513a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang Log.i(TAG, "Sending notification intent: " + intent); 3514a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang getContext().sendBroadcast(intent, null); 3515a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang } 3516a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang 35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static String sEventsTable = "Events"; 35189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static String sAttendeesTable = "Attendees"; 35199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static String sRemindersTable = "Reminders"; 35209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static String sCalendarAlertsTable = "CalendarAlerts"; 35219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static String sExtendedPropertiesTable = "ExtendedProperties"; 35229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 35239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int EVENTS = 1; 35249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int EVENTS_ID = 2; 35259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int INSTANCES = 3; 35269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int DELETED_EVENTS = 4; 35279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDARS = 5; 35289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDARS_ID = 6; 35299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int ATTENDEES = 7; 35309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int ATTENDEES_ID = 8; 35319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int REMINDERS = 9; 35329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int REMINDERS_ID = 10; 35339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int EXTENDED_PROPERTIES = 11; 35349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int EXTENDED_PROPERTIES_ID = 12; 35359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDAR_ALERTS = 13; 35369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDAR_ALERTS_ID = 14; 35379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final int CALENDAR_ALERTS_BY_INSTANCE = 15; 35386db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int INSTANCES_BY_DAY = 16; 35396db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int SYNCSTATE = 17; 35406db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int SYNCSTATE_ID = 18; 35416db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int EVENT_ENTITIES = 19; 35426db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int EVENT_ENTITIES_ID = 20; 35436db535b458146a279bebd4a51d56c1bdfc204528Erik private static final int EVENT_DAYS = 21; 354483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff private static final int SCHEDULE_ALARM = 22; 354583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff private static final int SCHEDULE_ALARM_REMOVE = 23; 354648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik private static final int TIME = 24; 354743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio private static final int CALENDAR_ENTITIES = 25; 354843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio private static final int CALENDAR_ENTITIES_ID = 26; 354981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final int INSTANCES_SEARCH = 27; 355081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang private static final int INSTANCES_SEARCH_BY_DAY = 28; 35519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 35529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 35539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final HashMap<String, String> sInstancesProjectionMap; 35549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final HashMap<String, String> sEventsProjectionMap; 355519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana private static final HashMap<String, String> sEventEntitiesProjectionMap; 35569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final HashMap<String, String> sAttendeesProjectionMap; 35579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final HashMap<String, String> sRemindersProjectionMap; 35589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff private static final HashMap<String, String> sCalendarAlertsProjectionMap; 35599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 35609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff static { 3561b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "instances/when/*/*", INSTANCES); 3562b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY); 356381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang sUriMatcher.addURI(Calendar.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH); 356481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang sUriMatcher.addURI(Calendar.AUTHORITY, "instances/searchbyday/*/*/*", 356581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang INSTANCES_SEARCH_BY_DAY); 3566b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS); 3567b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "events", EVENTS); 3568b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "events/#", EVENTS_ID); 3569b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities", EVENT_ENTITIES); 3570b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID); 3571b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "calendars", CALENDARS); 3572b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "calendars/#", CALENDARS_ID); 357343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES); 357443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID); 3575b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "deleted_events", DELETED_EVENTS); 3576b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "attendees", ATTENDEES); 3577b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "attendees/#", ATTENDEES_ID); 3578b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "reminders", REMINDERS); 3579b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "reminders/#", REMINDERS_ID); 3580b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES); 3581b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties/#", EXTENDED_PROPERTIES_ID); 3582b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS); 3583b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID); 3584b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/by_instance", 3585b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff CALENDAR_ALERTS_BY_INSTANCE); 3586c4e53191b570e09959c5723f4d253977ba48f2d0Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate", SYNCSTATE); 358783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate/#", SYNCSTATE_ID); 358883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_PATH, SCHEDULE_ALARM); 358983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE); 359048587d3291c4db7f0942e1bff55b88cfa7764ba0Erik sUriMatcher.addURI(Calendar.AUTHORITY, "time/#", TIME); 3591997e2e5cb006682bc1a82441304994b458d9745dErik sUriMatcher.addURI(Calendar.AUTHORITY, "time", TIME); 35929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 35939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap = new HashMap<String, String>(); 35949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Events columns 35959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.HTML_URI, "htmlUri"); 35969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.TITLE, "title"); 35979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.EVENT_LOCATION, "eventLocation"); 35989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.DESCRIPTION, "description"); 35999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.STATUS, "eventStatus"); 36009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus"); 36019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.COMMENTS_URI, "commentsUri"); 36029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.DTSTART, "dtstart"); 36039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.DTEND, "dtend"); 36049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone"); 36059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.DURATION, "duration"); 36069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.ALL_DAY, "allDay"); 36079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.VISIBILITY, "visibility"); 36089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.TRANSPARENCY, "transparency"); 36099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.HAS_ALARM, "hasAlarm"); 36109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties"); 36119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.RRULE, "rrule"); 36129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.RDATE, "rdate"); 36139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.EXRULE, "exrule"); 36149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.EXDATE, "exdate"); 36159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent"); 36169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime"); 36179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay"); 36189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.LAST_DATE, "lastDate"); 36199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData"); 36209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.CALENDAR_ID, "calendar_id"); 36219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers"); 36229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify"); 36239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests"); 36249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events.ORGANIZER, "organizer"); 36251b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio sEventsProjectionMap.put(Events.DELETED, "deleted"); 36269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3627e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff // Put the shared items into the Attendees, Reminders projection map 36281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap); 36291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap); 36301ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff 36319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Calendar columns 3632982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff sEventsProjectionMap.put(Calendars.COLOR, "color"); 3633982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff sEventsProjectionMap.put(Calendars.ACCESS_LEVEL, "access_level"); 3634982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff sEventsProjectionMap.put(Calendars.SELECTED, "selected"); 36351b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio sEventsProjectionMap.put(Calendars.SYNC1, "sync1"); 36369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Calendars.TIMEZONE, "timezone"); 36379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, "ownerAccount"); 36389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3639982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff // Put the shared items into the Instances projection map 3640e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff // The Instances and CalendarAlerts are joined with Calendars, so the projections include 3641e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff // the above Calendar columns. 3642982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap); 3643e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap); 3644982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff 36451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._ID, "_id"); 36461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._SYNC_ID, "_sync_id"); 36471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._SYNC_VERSION, "_sync_version"); 36481ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._SYNC_TIME, "_sync_time"); 3649c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank sEventsProjectionMap.put(Events._SYNC_DATA, "_sync_local_id"); 36501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._SYNC_DIRTY, "_sync_dirty"); 36511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "_sync_account"); 36529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE, 36531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff "_sync_account_type"); 36549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 365546f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff sEventEntitiesProjectionMap = new HashMap<String, String>(); 365619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.HTML_URI, "htmlUri"); 365719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.TITLE, "title"); 365819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.DESCRIPTION, "description"); 365919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, "eventLocation"); 366019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.STATUS, "eventStatus"); 366119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus"); 366219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.COMMENTS_URI, "commentsUri"); 366319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.DTSTART, "dtstart"); 366419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.DTEND, "dtend"); 366519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.DURATION, "duration"); 366619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone"); 366719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.ALL_DAY, "allDay"); 366819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.VISIBILITY, "visibility"); 366919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.TRANSPARENCY, "transparency"); 367019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.HAS_ALARM, "hasAlarm"); 367119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties"); 367219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.RRULE, "rrule"); 367319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.RDATE, "rdate"); 367419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.EXRULE, "exrule"); 367519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.EXDATE, "exdate"); 367619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent"); 367719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime"); 367819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay"); 367919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.LAST_DATE, "lastDate"); 368019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData"); 368119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, "calendar_id"); 368219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers"); 368319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify"); 368419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests"); 368519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events.ORGANIZER, "organizer"); 36861b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio sEventEntitiesProjectionMap.put(Events.DELETED, "deleted"); 368719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events._ID, Events._ID); 368819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID); 3689c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank sEventEntitiesProjectionMap.put(Events._SYNC_DATA, Events._SYNC_DATA); 369019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events._SYNC_VERSION, Events._SYNC_VERSION); 369119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana sEventEntitiesProjectionMap.put(Events._SYNC_DIRTY, Events._SYNC_DIRTY); 36921b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio sEventEntitiesProjectionMap.put(Calendars.SYNC1, Calendars.SYNC1); 369319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana 36949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Instances columns 36951b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted"); 36969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.BEGIN, "begin"); 36979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.END, "end"); 36989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id"); 36999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id"); 37009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.START_DAY, "startDay"); 37019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.END_DAY, "endDay"); 37029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute"); 37039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute"); 37049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Attendees columns 37069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id"); 37079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id"); 37089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName"); 37099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail"); 37109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus"); 37119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship"); 37129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType"); 37139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // Reminders columns 37159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id"); 37169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id"); 37179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sRemindersProjectionMap.put(Reminders.MINUTES, "minutes"); 37189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sRemindersProjectionMap.put(Reminders.METHOD, "method"); 37199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // CalendarAlerts columns 37219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id"); 37229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id"); 37239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin"); 37249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end"); 37259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime"); 37269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state"); 37279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes"); 37289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff /** 37319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Make sure that there are no entries for accounts that no longer 37329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * exist. We are overriding this since we need to delete from the 37339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendars table, which is not syncable, which has triggers that 37347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * will delete from the Events and tables, which are 37357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff * syncable. TODO: update comment, make sure deletes don't get synced. 37369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */ 37379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff public void onAccountsUpdated(Account[] accounts) { 3738ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio if (mDb == null) { 3739ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio mDb = mDbHelper.getWritableDatabase(); 3740ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 3741ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio if (mDb == null) { 3742ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio return; 3743ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio } 37449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 374546f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>(); 374646f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff HashSet<Account> validAccounts = new HashSet<Account>(); 37479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (Account account : accounts) { 37489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff validAccounts.add(new Account(account.name, account.type)); 37499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff accountHasCalendar.put(account, false); 37509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ArrayList<Account> accountsToDelete = new ArrayList<Account>(); 37529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.beginTransaction(); 37549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff try { 37559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (String table : new String[]{"Calendars"}) { 3757ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio // Find all the accounts the calendar DB knows about, mark the ones that aren't 37589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff // in the valid set for deletion. 37597cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Cursor c = mDb.rawQuery("SELECT DISTINCT " + 37607cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.SyncColumns._SYNC_ACCOUNT + 37617cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio "," + 37627cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.SyncColumns._SYNC_ACCOUNT_TYPE + 37637cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio " FROM " + table, null); 37649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff while (c.moveToNext()) { 37659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (c.getString(0) != null && c.getString(1) != null) { 37669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Account currAccount = new Account(c.getString(0), c.getString(1)); 37679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff if (!validAccounts.contains(currAccount)) { 37689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff accountsToDelete.add(currAccount); 37699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff c.close(); 37739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 37759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff for (Account account : accountsToDelete) { 37769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff Log.d(TAG, "removing data for removed account " + account); 37779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff String[] params = new String[]{account.name, account.type}; 37787cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio mDb.execSQL("DELETE FROM Calendars" + 37797cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio " WHERE " + 37807cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.SyncColumns._SYNC_ACCOUNT + "= ? AND " + 37817cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio Calendar.SyncColumns._SYNC_ACCOUNT_TYPE + "= ?", params); 37829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDbHelper.getSyncState().onAccountsChanged(mDb, accounts); 37849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.setTransactionSuccessful(); 37859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } finally { 37869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff mDb.endTransaction(); 37879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37883ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang 37893ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang // make sure the widget reflects the account changes 3790dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang sendUpdateNotification(false); 37919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 37929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3793595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff /* package */ static boolean readBooleanQueryParameter(Uri uri, String name, 3794595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff boolean defaultValue) { 3795595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff final String flag = getQueryParameter(uri, name); 37969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff return flag == null 37979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff ? defaultValue 37989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff : (!"false".equals(flag.toLowerCase()) && !"0".equals(flag.toLowerCase())); 37999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff } 38009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff 3801595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff // Duplicated from ContactsProvider2. TODO: a utility class for shared code 3802595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff /** 3803595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff * A fast re-implementation of {@link Uri#getQueryParameter} 3804595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff */ 3805595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff /* package */ static String getQueryParameter(Uri uri, String parameter) { 3806595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff String query = uri.getEncodedQuery(); 3807595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (query == null) { 3808595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff return null; 3809595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3810595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3811595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff int queryLength = query.length(); 3812595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff int parameterLength = parameter.length(); 3813595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3814595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff String value; 3815595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff int index = 0; 3816595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff while (true) { 3817595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff index = query.indexOf(parameter, index); 3818595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (index == -1) { 3819595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff return null; 3820595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3821595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3822595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff index += parameterLength; 3823595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3824595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (queryLength == index) { 3825595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff return null; 3826595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3827595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3828595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (query.charAt(index) == '=') { 3829595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff index++; 3830595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff break; 3831595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3832595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3833595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3834595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff int ampIndex = query.indexOf('&', index); 3835595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff if (ampIndex == -1) { 3836595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff value = query.substring(index); 3837595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } else { 3838595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff value = query.substring(index, ampIndex); 3839595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3840595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff 3841595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff return Uri.decode(value); 3842595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff } 3843636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff 3844636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff /** 3845636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * Inserts an argument at the beginning of the selection arg list. 3846636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * 3847636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is 3848636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * prepended to the user's where clause (combined with 'AND') to generate 3849636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * the final where close, so arguments associated with the QueryBuilder are 3850636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff * prepended before any user selection args to keep them in the right order. 3851636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff */ 3852636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff private String[] insertSelectionArg(String[] selectionArgs, String arg) { 3853636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff if (selectionArgs == null) { 3854636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return new String[] {arg}; 3855636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff } else { 3856636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff int newLength = selectionArgs.length + 1; 3857636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff String[] newSelectionArgs = new String[newLength]; 3858636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff newSelectionArgs[0] = arg; 3859636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length); 3860636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff return newSelectionArgs; 3861636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff } 3862636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff } 3863ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio}