CalendarProvider2.java revision fc30eb24b8ed12dec09957479f489f67cc43b42b
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
208bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglioimport android.os.PowerManager;
218bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglioimport android.os.SystemClock;
227be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
237be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
24370f91c0cfe5a5fecaba6120e703f4d2271d2277Erikimport com.google.common.annotations.VisibleForTesting;
25370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.AlarmManager;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.PendingIntent;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Debug;
46a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
47a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
49f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglioimport android.pim.EventRecurrence;
509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet;
519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Attendees;
549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.CalendarAlerts;
559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Calendars;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Events;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Instances;
589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Reminders;
599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
601edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
61192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
64ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
67ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
70dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Set;
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
738bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglioimport java.util.concurrent.atomic.AtomicBoolean;
74dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * is defined in {@link android.provider.Calendar}.
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
838bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
857be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
867be45683e367bd6897daf6444b03be938f8f5eaaErik
879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean PROFILE = false;
889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
898f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
908f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff    private static final String INVALID_CALENDARALERTS_SELECTOR =
91d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang            "_id IN (SELECT ca." + CalendarAlerts._ID + " FROM "
92d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + Tables.CALENDAR_ALERTS + " AS ca"
93d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Tables.INSTANCES
94d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " USING (" + Instances.EVENT_ID + ","
95d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + Instances.BEGIN + "," + Instances.END + ")"
96d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Tables.REMINDERS + " AS r ON"
97d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " (ca." + CalendarAlerts.EVENT_ID + "=r." + Reminders.EVENT_ID
98d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " AND ca." + CalendarAlerts.MINUTES + "=r." + Reminders.MINUTES + ")"
99d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Views.EVENTS + " AS e ON"
100d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " (ca." + CalendarAlerts.EVENT_ID + "=e." + Events._ID + ")"
101d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " WHERE " + Tables.INSTANCES + "." + Instances.BEGIN + " ISNULL"
102d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR ca." + CalendarAlerts.ALARM_TIME + "<?"
103d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR (r." + Reminders.MINUTES + " ISNULL"
104d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "       AND ca." + CalendarAlerts.MINUTES + "<>0)"
105d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR e." + Calendars.SELECTED + "=0)";
1068f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
1071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
1081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
1099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
1129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
1149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
1159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
1197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3;
1207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
1237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
1247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
129646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
130646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
131646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
132646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
133646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
134646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
135646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
136646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
137646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
138646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
139646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
140646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
141646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
142646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
143646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
144646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
150ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
1539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false)
15583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true)
15683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // TODO: use a service to schedule alarms rather than private URI
15783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_PATH = "schedule_alarms";
15883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
15983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_URI =
16083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_PATH);
16183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_REMOVE_URI =
16283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
1639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1648bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static final String REMOVE_ALARM_VALUE = "removeAlarms";
1658bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
1669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // To determine if a recurrence exception originally overlapped the
1679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // window, we need to assume a maximum duration, since we only know
1689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // the original start time.
1699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int MAX_ASSUMED_DURATION = 7*24*60*60*1000;
1709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1718ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
1728ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // Due to an issue in Calendar Server restricting the length of the name we had to strip it down
1738ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1748ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1758ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1768ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1778ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1783443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
179c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.EVENT_ID + ", " +
180c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.DTSTART_2445 + ", " +
181c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.DTEND_2445 + ", " +
1823443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1833443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
184b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
185b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1863443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
187b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Calendar.EventsRawTimesColumns.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
188b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
189b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_UPDATE_EVENT_SET_DIRTY = "UPDATE " +
190b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
191b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " SET " + Events._SYNC_DIRTY + "=1" +
192b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
193b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
194b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ID = BaseColumns._ID + "=?";
195b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EVENT_ID = "event_id=?";
196b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ORIGINAL_EVENT = Events.ORIGINAL_EVENT + "=?";
197b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " +
199b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID;
200b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
201b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
202b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
203b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID;
204b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
205b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
206b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Views.EVENTS + "." + BaseColumns._ID + "=" +
207b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
208b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
209b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
210b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Views.EVENTS + "." + BaseColumns._ID + "=" +
211b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
212b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
213b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
214b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
215b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
216b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EXTENDED_PROPERTIES + "." + Calendar.ExtendedProperties._ID + "=?";
217b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
218b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_GET_EVENTS_ENTRIES =
219b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "((" + Events.DTSTART + " <= ? AND "
220b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    + "(" + Events.LAST_DATE + " IS NULL OR " + Events.LAST_DATE + " >= ?)) OR " +
221b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "(" + Events.ORIGINAL_INSTANCE_TIME + " IS NOT NULL AND " +
222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Events.ORIGINAL_INSTANCE_TIME + " <= ? AND " +
223b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Events.ORIGINAL_INSTANCE_TIME + " >= ?)) AND " +
224b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "(" + Calendars.SYNC_EVENTS + " != 0)";
225b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
226b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_SELECT_EVENTS_SYNC_ID =
227b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "SELECT " + Events._SYNC_ID +
228b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " FROM " + Tables.EVENTS +
229b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + SQL_WHERE_ID;
230b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
231b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
232b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                " WHERE " + Calendar.SyncColumns._SYNC_ACCOUNT + "=? AND " +
233b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Calendar.SyncColumns._SYNC_ACCOUNT_TYPE + "=?";
234b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
235b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ID_FROM_INSTANCES_NOT_SYNCED =
236b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            BaseColumns._ID + " IN " +
237b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "(SELECT " + Tables.INSTANCES + "." + BaseColumns._ID + " as _id" +
238b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " FROM " + Tables.INSTANCES +
239b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " INNER JOIN " + Tables.EVENTS +
240b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " ON (" +
241b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID +
242b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            ")" +
243b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Tables.EVENTS + "." + Events._ID + "=?)";
244b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
245b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ID_FROM_INSTANCES_SYNCED =
246b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            BaseColumns._ID + " IN " +
247b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            "(SELECT " + Tables.INSTANCES + "." + BaseColumns._ID + " as _id" +
248b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " FROM " + Tables.INSTANCES +
249b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " INNER JOIN " + Tables.EVENTS +
250b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " ON (" +
251b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID +
252b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            ")" +
253b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Tables.EVENTS + "." + Events._SYNC_ID + "=?" + " OR " +
254b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.EVENTS + "." + Events.ORIGINAL_EVENT + "=?)";
255b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
256fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
257fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
258fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2598bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private final AtomicBoolean mNextAlarmCheckScheduled = new AtomicBoolean(false);
2608bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private final AtomicBoolean mNeedRemoveAlarms = new AtomicBoolean(false);
2618bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
2628bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String ACTION_CHECK_NEXT_ALARM =
2638bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            "com.android.providers.calendar.intent.CalendarProvider2";
264e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private static final int ALARM_CHECK_DELAY_MILLIS = 5000;
2658bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
266b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    public static final class InstancesList extends ArrayList<ContentValues> {
2679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
269b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    public static final class EventInstancesMap extends HashMap<String, InstancesList> {
2701030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        public void add(String syncIdKey, ContentValues values) {
2711030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList instances = get(syncIdKey);
2729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (instances == null) {
2739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                instances = new InstancesList();
2741030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                put(syncIdKey, instances);
2759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instances.add(values);
2779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We search backward in time for event reminders that we may have missed
2829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and schedule them if the event has not yet expired.  The amount in
2839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the past to search backwards is controlled by this constant.  It
2849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should be at least a few minutes to allow for an event that was
2859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * recently created on the web to make its way to the phone.  Two hours
2869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * might seem like overkill, but it is useful in the case where the user
2879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * just crossed into a new timezone and might have just missed an alarm.
2889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2891edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff    private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS;
2909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Alarms older than this threshold will be deleted from the CalendarAlerts
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  This should be at least a day because if the timezone is
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * wrong and the user corrects it we might delete good alarms that
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * appear to be old because the device time was incorrectly in the future.
2969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This threshold must also be larger than SCHEDULE_ALARM_SLACK.  We add
2979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the SCHEDULE_ALARM_SLACK to ensure this.
2989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
2999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * To make it easier to find and debug problems with missed reminders,
3009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * set this to something greater than a day.
3019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long CLEAR_OLD_ALARM_THRESHOLD =
3031edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            7 * DateUtils.DAY_IN_MILLIS + SCHEDULE_ALARM_SLACK;
3049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3058bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    // A lock for synchronizing access to the AlarmManager
3069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Object mAlarmLock = new Object();
3079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
3099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
3109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
3119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
3129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
3149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
3159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
31781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
31881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
31981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
32081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
32181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        + Calendar.Instances.EVENT_ID + "=" +
32281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
32381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        + Calendar.Events._ID + ")";
32481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
32518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
32618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
32718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
32818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
32918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
33018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Instances.EVENT_ID + "=" +
33118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
33218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Events._ID + ")" + ") LEFT OUTER JOIN " +
33318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
33418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
33518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Attendees.EVENT_ID + "=" +
33618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
33718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Events._ID + ")";
33818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
339b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
34081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.START_DAY + "<=? AND " +
34181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.END_DAY + ">=?";
34281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
343b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
34481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.BEGIN + "<=? AND " +
34581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.END + ">=?";
3469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
3489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
3499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
3509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
3519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
3529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
35481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * A regex for describing how we split search queries into tokens.
355dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Keeps quoted phrases as one token.
356dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
357dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *   "one \"two three\"" ==> ["one" "two three"]
358dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
359dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
360dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
361dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
362dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
363dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
364dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
365dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
366dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
367dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
36881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
369dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
370dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
371dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
372dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
373dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
374dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
375dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
376dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
377dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
37881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
37918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
38018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
38118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
38218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
38318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
38418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        "group_concat(" + Calendar.Attendees.ATTENDEE_EMAIL + ")";
38518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
38618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
38718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
38818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
38918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
39018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
39118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        "group_concat(" + Calendar.Attendees.ATTENDEE_NAME + ")";
39218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
39381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
39481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Events.TITLE,
39581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Events.DESCRIPTION,
39618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        Calendar.Events.EVENT_LOCATION,
39718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
39818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
39981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
40081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
401e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private CalendarAlarmManager mAlarmManager;
4029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
403a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
404a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
405a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
406a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
407a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
408a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
409a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
410a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
411a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
412a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
413a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
414a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
415dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
416dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
417dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
418dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
419dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
420dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
421e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
422e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
4238bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
4248bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4258bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private volatile PowerManager.WakeLock mScheduleNextAlarmWakeLock;
4268bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static final String SCHEDULE_NEXT_ALARM_WAKE_LOCK = "ScheduleNextAlarmWakeLock";
427a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
428a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
429a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
430a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
431dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
432a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
433a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
434a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
435dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
436dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
437dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
438dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
439dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
440a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
441a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
442a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
443a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
4579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void verifyAccounts() {
4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4798bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4808bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4818bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4828bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
483e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    PowerManager.WakeLock getScheduleNextAlarmWakeLock() {
484e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mScheduleNextAlarmWakeLock == null) {
485e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
486e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            // Create a wake lock that will be used when we are actually scheduling the next alarm
487e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mScheduleNextAlarmWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
488e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                    SCHEDULE_NEXT_ALARM_WAKE_LOCK);
489e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            // We want the Wake Lock to be reference counted (so that we dont need to take care
490e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            // about its reference counting)
491e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mScheduleNextAlarmWakeLock.setReferenceCounted(true);
492e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
493e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        return mScheduleNextAlarmWakeLock;
494e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
495e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
496e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    void acquireScheduleNextAlarmWakeLock() {
497e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getScheduleNextAlarmWakeLock().acquire();
4988bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4998bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
500e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    void releaseScheduleNextAlarmWakeLock() {
501e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getScheduleNextAlarmWakeLock().release();
502e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
503e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
504e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
505e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
506e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
507e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
508e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
509e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
510e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
5118bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
5128bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
5149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
5159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
516ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
517ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
518ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
519f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
520f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
521f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
522ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
523ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
524ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
5259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
526ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
5278bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
5288bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
529dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
530e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
531e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
532ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
533ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
5369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
5379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
5399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
5419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
5439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
5449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
5459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
546e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
5479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mMetaData = new MetaData(mDbHelper);
549ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
550ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
551e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getOrCreateCalendarAlarmManager();
5529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
553e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getScheduleNextAlarmWakeLock();
554e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
555e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
5568bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
5589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
560e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    protected CalendarAlarmManager createCalendarAlarmManager() {
561e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        return new CalendarAlarmManager(mContext);
562e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
563e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
564e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
565e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mAlarmManager == null) {
566e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mAlarmManager = createCalendarAlarmManager();
567e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
568e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        return mAlarmManager;
569e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
570e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
571ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
572ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
573ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
574ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
575ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
576ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
577ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
578ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
579ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
580ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
581ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
582ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
583ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
584ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
585ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
586ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
5879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
590315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
5919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
5939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
5959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
5989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
5999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
6009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
601ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
6029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
606315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
607315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
608315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
609315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
610315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
611315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
612315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
613315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
6149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
6159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
6169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
617315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
618ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
619315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
620315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
621a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
622315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
623315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
624315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
625ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
626315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
627315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
628ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
629315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
630ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
631ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
632ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
633ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
634ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                rescheduleMissedAlarms();
635ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
636ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
637f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
638f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
639f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
640ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
641ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
642ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
643ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
644ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
645f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
646f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
647f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
648ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
6499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
650ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
651ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
652315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
653ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
654ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
6553443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
656ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
657315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
659ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
660ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
661ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
662ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
663ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
664ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
6653443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
6663443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
667ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
668ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
669ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
670ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
671ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6723443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
673f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
674f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
675f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
676f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
677f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
678f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
679f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
680ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6813443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
682ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
683ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
684ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
685ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
686ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
687ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
688ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
689ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
690ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
691ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
692ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
693f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
694f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
695f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
696ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
697ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
698ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
699ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
700ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
701ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
702f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
703f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
704f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
705ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
706ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
707ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
708ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
709ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
710ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
711ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
712ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
713ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
714b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
715b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
716ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
717b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
718dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
719ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
720ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
721ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
722ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
723ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
724ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
725ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
726ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
727ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
728ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
729ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
730f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
731f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
732f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
733ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
734ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
7359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
736ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
737ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
738ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
739ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
740315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
741315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
742ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
743ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
744ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
745ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
746ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
74725e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
748ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
749315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
750315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
751ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
752ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
753f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
754f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
755f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
756ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
757ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
758ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
759315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
760315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
761315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
762315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
763315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
764ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
7659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
7669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
7679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
7689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
769315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
770315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7761f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7791f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7801f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7811f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7821f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7831f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7841f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
785d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    null /* selection */, null /* sort */,
786d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
787315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
788315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
789315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone());
7901f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
7911f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
7921f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
7931f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7941f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rescheduleMissedAlarms();
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void rescheduleMissedAlarms() {
800e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getOrCreateCalendarAlarmManager().rescheduleMissedAlarms(mContentResolver);
8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
804b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
807e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver.notifyChange(Calendar.CONTENT_URI, null, syncToNetwork);
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
813ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
814ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
8189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
8209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
822315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
8239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
8269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
8289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
8299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
8311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
833595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
8349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
8361ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
838636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
839b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
84119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
84219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
84319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
84419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
845595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
84619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
84719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
84819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
84919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
850636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
851b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
85219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
85319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
8549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
85543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
856b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
857595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
8589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
86043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
861b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
862636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
863b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
8669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
8699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
881315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return handleInstanceQuery(qb, begin, end, projection,
883d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                        selection, sortOrder, match == INSTANCES_BY_DAY,
884315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        false /* do not force Instances deletion and expansion */,
885315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
88681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
88781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
88881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
88981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
89081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
89181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
89281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
89381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
89481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
89581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
89681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
89781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
89881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
89981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
900315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
90181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
90281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
90381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                return handleInstanceSearchQuery(qb, begin, end, query, projection,
904315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        selection, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
905315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9066db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
9099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
9119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
9149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
9179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
9199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
921315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
922315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
923315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
925b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS);
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
927b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(Tables.EVENTS + "." + Events._ID + "="
928b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        + Tables.ATTENDEES + "." + Attendees.EVENT_ID);
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
931b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS);
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
933636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
934b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
937b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
940b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS);
9419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
942636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
943b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
9449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
946b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
948b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
951b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
953b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
957b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
959636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
960b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
9619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
963b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
966b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
967636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
968b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
9699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
970315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
971b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
972315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
973315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
9809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
9839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
9849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
985ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
986ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
987ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
988ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
989ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
990ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
991ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
992ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
993ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
9949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
9959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
9969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
9979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
998e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            c.setNotificationUri(mContentResolver, Calendar.Events.CONTENT_URI);
9999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
10049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
10069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
10079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
10089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
10099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
10109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
10139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
1014d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1015315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1016315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
10179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
10189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
1020d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio            long rangeEnd, String[] projection, String selection, String sort,
1021315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean searchByDay, boolean forceExpansion, String instancesTimezone,
1022315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
10239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
102481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
10259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
10269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
10279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
10289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
1029315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
10309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
10319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
10329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
10339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
10349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1035315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
1036315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1037b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1040315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
1041315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1042b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
10439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10448335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(rangeEnd),
10458335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
10468335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
10477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
10489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
105081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
1051dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1052dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1053dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1054dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1055dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1056dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1057dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1058dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1059dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1060dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1061dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
106281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1063dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1064dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
106581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
106681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
106781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
106881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
106981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
107081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1071dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1072dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1073dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1074dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1075dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1076dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1077dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1078dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1079dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1080dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1081dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1082dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1083dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1084dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
108581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
108681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
108781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
108881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
108981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
109081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
109181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
109281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
109381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
109481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
109581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
109681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
109781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
109881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
109981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
110081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
110181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
110281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
110381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
110481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * approximation of full-text search across multiple columns.
110581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
110681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
110781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
110881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
110981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
111081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
111181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
111281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
111381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
111481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
111581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
111681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1117dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1118dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1119dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
112081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
112181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
112281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
112381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
112418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
112518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
112618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
112718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
112881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
112981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
113081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
113181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
113281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
113381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
113418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
113518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
113681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
113718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
113818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
113918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
114081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1141f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1142f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
114318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
114481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
114581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
114681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
114781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
114881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
114981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
115081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
1151315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String selection, String sort, boolean searchByDay, String instancesTimezone,
1152315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
115318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
115481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
115581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1156dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
115781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String[] selectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
115818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
115918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1160dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1161dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
116281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
116381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
116481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1165315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
116681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
116781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
116881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
116981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
117081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
117118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
117218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
117356292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
117456292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1175315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1176315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1177315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
117856292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1179b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
118081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
118181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
118218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
118318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
118456292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
118556292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1186315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1187315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1188315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
118956292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1190b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
119181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
119281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
119318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
119418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                Instances._ID /* groupBy */, searchWhere /* having */, sort);
119581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
119681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
11976db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1198315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1199315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
120081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
12016db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
120243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
120343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1204315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1205192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
120643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
120743556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1208192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
120943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1210315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1211315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1212b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
12138335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
12148335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
12158335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
12166db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
12179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  Acquires the database lock and calls {@link #acquireInstanceRangeLocked}.
12229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1226d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1227315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1228315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1230d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1231315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1232315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
12339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
12349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1235315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1236315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
12379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
12399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
12469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1250315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1251315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1252315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1254315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1255315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
12569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
12579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
12589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1259315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1260315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1261315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1263315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
12649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
12659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
12669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
12679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
12689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
12739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
12799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
12819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1282315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1283315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1284315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1285315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1286315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1287315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
12897be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
12907be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
12917be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
12927be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1293315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1294315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1295315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1296d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1298b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1299f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13006db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1303315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1304315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1305315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1307315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
13087be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
13097be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
13107be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
13117be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
13127be45683e367bd6897daf6444b03be938f8f5eaaErik
13137be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1314315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
13157be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
13167be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
13177be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
13187be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1319315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1335f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1345315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1352315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1357315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EXPAND_COLUMNS = new String[] {
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._ID,
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.STATUS,
13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTSTART,
13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTEND,
13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EVENT_TIMEZONE,
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
13689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
13699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXRULE,
13709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXDATE,
13719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DURATION,
13729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ALL_DAY,
13739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
13741030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            Events.ORIGINAL_INSTANCE_TIME,
13751dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio            Events.CALENDAR_ID,
13761b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio            Events.DELETED
13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
13789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make instances for the given range.
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void expandInstanceRangeLocked(long begin, long end, String localTimezone) {
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.startMethodTracing("expandInstanceRangeLocked");
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Expanding events between " + begin + " and " + end);
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getEntries(begin, end);
13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            performInstanceExpansion(begin, end, localTimezone, entries);
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
13979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
13989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.stopMethodTracing();
14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Get all entries affecting the given window.
14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
14099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return Cursor for the entries; caller must close it.
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getEntries(long begin, long end) {
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
14131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String beginString = String.valueOf(begin);
14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String endString = String.valueOf(end);
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // grab recurrence exceptions that fall outside our expansion window but modify
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences that do fall within our window.  we won't insert these into the output
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // set of instances, but instead will just add them to our cancellations list, so we
14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // can cancel the correct recurrence expansion instances.
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we don't have originalInstanceDuration or end time.  for now, assume the original
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // instance lasts no longer than 1 week.
14252d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio        // also filter with syncable state (we dont want the entries from a non syncable account)
14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: compute the originalInstanceEndTime or get this from the server.
1427b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_GET_EVENTS_ENTRIES);
14288335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {endString, beginString, endString,
14298335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(begin - MAX_ASSUMED_DURATION)};
1430e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        Cursor c = qb.query(mDb, EXPAND_COLUMNS, null /* selection */,
14318335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                selectionArgs, null /* groupBy */,
14327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
1433e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1434e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            Log.v(TAG, "Instance expansion:  got " + c.getCount() + " entries");
1435e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
1436e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        return c;
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
14401030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * Generates a unique key from the syncId and calendarId.
14411030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * The purpose of this is to prevent collisions if two different calendars use the
14421030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * same sync id.  This can happen if a Google calendar is accessed by two different accounts,
14431030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * or with Exchange, where ids are not unique between calendars.
14441030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param syncId Id for the event
14451030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param calendarId Id for the calendar
14461030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @return key
14471030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     */
14481030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    private String getSyncIdKey(String syncId, long calendarId) {
14491030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        return calendarId + ":" + syncId;
14501030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    }
14511030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
14521030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    /**
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Perform instance expansion on the given entries.
14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
14569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param localTimezone
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param entries The entries to process.
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void performInstanceExpansion(long begin, long end, String localTimezone,
14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                          Cursor entries) {
14619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        RecurrenceProcessor rp = new RecurrenceProcessor();
14629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14631030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // Key into the instance values to hold the original event concatenated with calendar id.
14641030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        final String ORIGINAL_EVENT_AND_CALENDAR = "ORIGINAL_EVENT_AND_CALENDAR";
14651030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int statusColumn = entries.getColumnIndex(Events.STATUS);
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
14689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtendColumn = entries.getColumnIndex(Events.DTEND);
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int durationColumn = entries.getColumnIndex(Events.DURATION);
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rruleColumn = entries.getColumnIndex(Events.RRULE);
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rdateColumn = entries.getColumnIndex(Events.RDATE);
14739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exruleColumn = entries.getColumnIndex(Events.EXRULE);
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exdateColumn = entries.getColumnIndex(Events.EXDATE);
14759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int idColumn = entries.getColumnIndex(Events._ID);
14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
14801030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        int calendarIdColumn = entries.getColumnIndex(Events.CALENDAR_ID);
14811b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        int deletedColumn = entries.getColumnIndex(Events.DELETED);
14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues initialValues;
14849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        EventInstancesMap instancesMap = new EventInstancesMap();
14859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Duration duration = new Duration();
14879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time eventTime = new Time();
14889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: entries contains all events that affect the current
14909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // window.  It consists of:
14919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  These will be
14929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed.
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Recurrences that included the window.  These will be displayed
14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    if not canceled.
14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Recurrence exceptions that fall in the window.  These will be
14969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed if not cancellations.
14979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
14989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    window (subject to 1 week assumption above), but are outside
14999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    the window.  These will not be displayed.  Cases c and d are
15009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    distingushed by the start / end time.
15019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        while (entries.moveToNext()) {
15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                initialValues = null;
15059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                boolean allDay = entries.getInt(allDayColumn) != 0;
15079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String eventTimezone = entries.getString(eventTimezoneColumn);
15099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (allDay || TextUtils.isEmpty(eventTimezone)) {
15109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // in the events table, allDay events start at midnight.
15119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this forces them to stay at midnight for all day events
15129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: check that this actually does the right thing.
15139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTimezone = Time.TIMEZONE_UTC;
15149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
15159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long dtstartMillis = entries.getLong(dtstartColumn);
15179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Long eventId = Long.valueOf(entries.getLong(idColumn));
15189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String durationStr = entries.getString(durationColumn);
15209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (durationStr != null) {
15219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    try {
15229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.parse(durationStr);
15239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    catch (DateException e) {
1525f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1526f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.w(TAG, "error parsing duration for event "
1527f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + eventId + "'" + durationStr + "'", e);
1528f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
15299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.sign = 1;
15309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.weeks = 0;
15319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.days = 0;
15329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.hours = 0;
15339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.minutes = 0;
15349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.seconds = 0;
15359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        durationStr = "+P0S";
15369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
15389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String syncId = entries.getString(syncIdColumn);
15409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String originalEvent = entries.getString(originalEventColumn);
15419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalInstanceTimeMillis = -1;
15439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!entries.isNull(originalInstanceTimeColumn)) {
15449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn);
15459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
15469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int status = entries.getInt(statusColumn);
15471dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                boolean deleted = (entries.getInt(deletedColumn) != 0);
15489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rruleStr = entries.getString(rruleColumn);
15509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rdateStr = entries.getString(rdateColumn);
15519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exruleStr = entries.getString(exruleColumn);
15529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exdateStr = entries.getString(exdateColumn);
15531030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                long calendarId = entries.getLong(calendarIdColumn);
15541030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String syncIdKey = getSyncIdKey(syncId, calendarId); // key into instancesMap
15559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1556f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                RecurrenceSet recur = null;
1557f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                try {
1558f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr);
1559f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                } catch (EventRecurrence.InvalidFormatException e) {
1560f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
1561f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "Could not parse RRULE recurrence string: " + rruleStr, e);
1562f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
1563f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    continue;
1564f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                }
15659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1566f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                if (null != recur && recur.hasRecurrence()) {
15679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is repeating
15689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (status == Events.STATUS_CANCELED) {
15709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen!
1571f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1572f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.e(TAG, "Found canceled recurring event in "
1573f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + "Events table.  Ignoring.");
1574f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
15759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        continue;
15769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
1577370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    if (deleted) {
157806c61055fc7a1504eefcd6c9a471f7370526e532Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.DEBUG)) {
157906c61055fc7a1504eefcd6c9a471f7370526e532Fabrice Di Meglio                            Log.d(TAG, "Found deleted recurring event in "
158006c61055fc7a1504eefcd6c9a471f7370526e532Fabrice Di Meglio                                    + "Events table.  Ignoring.");
158106c61055fc7a1504eefcd6c9a471f7370526e532Fabrice Di Meglio                        }
1582370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                        continue;
1583370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    }
15849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // need to parse the event into a local calendar.
15869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.timezone = eventTimezone;
15879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.set(dtstartMillis);
15889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.allDay = allDay;
15899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
15919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen.
1592f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1593f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.e(TAG, "Repeating event has no duration -- "
1594f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + "should not happen.");
1595f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
15969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (allDay) {
15979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // set to one day.
15989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
15999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
16009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 1;
16019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
16029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
16039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.seconds = 0;
16049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            durationStr = "+P1D";
16059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
16069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // compute the duration from dtend, if we can.
16079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // otherwise, use 0s.
16089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
16099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
16109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 0;
16119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
16129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
16139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            if (!entries.isNull(dtendColumn)) {
16149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                long dtendMillis = entries.getLong(dtendColumn);
16159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000);
16169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P" + duration.seconds + "S";
16179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            } else {
16189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = 0;
16199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P0S";
16209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            }
16219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
16229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long[] dates;
16259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    dates = rp.expand(eventTime, recur, begin, end);
16269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Initialize the "eventTime" timezone outside the loop.
16289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This is used in computeTimezoneDependentFields().
16299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
16309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
16319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
16329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
16339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long durationMillis = duration.getMillis();
16369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    for (long date : dates) {
16379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues = new ContentValues();
16389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.EVENT_ID, eventId);
16399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.BEGIN, date);
16419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        long dtendMillis = date + durationMillis;
16429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.END, dtendMillis);
16439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        computeTimezoneDependentFields(date, dtendMillis,
16459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                eventTime, initialValues);
16461030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        instancesMap.add(syncIdKey, initialValues);
16479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else {
16499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is not repeating
16509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues = new ContentValues();
16519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // if this event has an "original" field, then record
16539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // that we need to cancel the original event (we can't
16549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // do that here because the order of this loop isn't
16559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // defined)
16569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (originalEvent != null && originalInstanceTimeMillis != -1) {
16571030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // The ORIGINAL_EVENT_AND_CALENDAR holds the
16581030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // calendar id concatenated with the ORIGINAL_EVENT to form
16591030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // a unique key, matching the keys for instancesMap.
16601030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        initialValues.put(ORIGINAL_EVENT_AND_CALENDAR,
16611030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                                getSyncIdKey(originalEvent, calendarId));
16629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.ORIGINAL_INSTANCE_TIME,
16639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                originalInstanceTimeMillis);
16649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.STATUS, status);
16659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long dtendMillis = dtstartMillis;
16689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
16699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!entries.isNull(dtendColumn)) {
16709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            dtendMillis = entries.getLong(dtendColumn);
16719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
16729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
16739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        dtendMillis = duration.addTo(dtstartMillis);
16749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this non-recurring event might be a recurrence exception that doesn't
16779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // actually fall within our expansion window, but instead was selected
16789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so we can correctly cancel expanded recurrence instances below.  do not
16799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // add events to the instances map if they don't actually fall within our
16809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // expansion window.
16819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if ((dtendMillis < begin) || (dtstartMillis > end)) {
16829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (originalEvent != null && originalInstanceTimeMillis != -1) {
16839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            initialValues.put(Events.STATUS, Events.STATUS_CANCELED);
16849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
1685f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
1686f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                Log.w(TAG, "Unexpected event outside window: " + syncId);
1687f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            }
16889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            continue;
16899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
16909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.EVENT_ID, eventId);
16939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16941dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    initialValues.put(Instances.BEGIN, dtstartMillis);
16959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.END, dtendMillis);
16969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16971dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    // we temporarily store the DELETED status (will be cleaned later)
16981b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    initialValues.put(Events.DELETED, deleted);
16991dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
17009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
17019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
17029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
17039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
17049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
17059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    computeTimezoneDependentFields(dtstartMillis, dtendMillis,
17069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            eventTime, initialValues);
17079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17081030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                    instancesMap.add(syncIdKey, initialValues);
17099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (DateException e) {
1711f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
1712f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
1713f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
17149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (TimeFormatException e) {
1715f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
1716f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
1717f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
17189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains all instances that affect the
17221030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // window, indexed by original sync id concatenated with calendar id.
17231030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // It consists of:
17249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  They have:
17259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
17269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window.  They may
17279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be subject to exceptions.  They have:
17289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
17299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They have:
17301030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS (since they can
17319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be a modification or cancellation), EVENT_ID, BEGIN, END
17329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
17339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  They have:
17341030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS =
17359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED, EVENT_ID, BEGIN, END
17369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // First, delete the original instances corresponding to recurrence
17389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // exceptions.  We do this by iterating over the list and for each
17399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrence exception, we search the list for an instance with a
17409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // matching "original instance time".  If we find such an instance,
17419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we remove it from the list.  If we don't find such an instance
17429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then we cancel the recurrence exception.
17439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Set<String> keys = instancesMap.keySet();
17441030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
17451030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
17469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
17479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // If this instance is not a recurrence exception, then
17499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // skip it.
17501030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                if (!values.containsKey(ORIGINAL_EVENT_AND_CALENDAR)) {
17519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
17529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17541030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String originalEventPlusCalendar = values.getAsString(ORIGINAL_EVENT_AND_CALENDAR);
17559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
17561030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                InstancesList originalList = instancesMap.get(originalEventPlusCalendar);
17579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (originalList == null) {
17589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // The original recurrence is not present, so don't try canceling it.
17599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
17609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Search the original event for a matching original
17639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance time.  If there is a matching one, then remove
17649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the original one.  We do this both for exceptions that
17659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // change the original instance as well as for exceptions
17669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // that delete the original instance.
17679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                for (int num = originalList.size() - 1; num >= 0; num--) {
17689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    ContentValues originalValues = originalList.get(num);
17699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long beginTime = originalValues.getAsLong(Instances.BEGIN);
17709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (beginTime == originalTime) {
17719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // We found the original instance, so remove it.
17729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        originalList.remove(num);
17739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
17749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains filtered instances.
17799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // It consists of:
17809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.
17819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window and have not
17829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   been subject to exceptions.
17839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They will have
17849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED if they are cancellations.
17859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
17869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  These are STATUS_CANCELED.
17879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do the inserts.  Since the db lock is held when this method is executed,
17899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // this will be done in a transaction.
17909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
17919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // while the calendar app is trying to query the db (expanding instances)), we will
17929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // not be "polite" and yield the lock until we're done.  This will favor local query
17939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // operations over sync/write operations.
17941030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
17951030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
17969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
17979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17981dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // If this instance was cancelled or deleted then don't create a new
17999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance.
18009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer status = values.getAsInteger(Events.STATUS);
18011b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                boolean deleted = values.containsKey(Events.DELETED) ?
18021b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                        values.getAsBoolean(Events.DELETED) : false;
18031dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                if ((status != null && status == Events.STATUS_CANCELED) || deleted) {
18049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
18059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18071dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // We remove this useless key (not valid in the context of Instances table)
18081b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                values.remove(Events.DELETED);
18091dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
18109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Remove these fields before inserting a new instance
18111030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                values.remove(ORIGINAL_EVENT_AND_CALENDAR);
18129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
18139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.STATUS);
18149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1815c874ed5c6cc0fcc6ac06ae7d20db0eab7d749608Ken Shirriff                mDbHelper.instancesReplace(values);
18169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
18219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Computes the timezone-dependent fields of an instance of an event and
18229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * updates the "values" map to contain those fields.
18239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
18249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin the start time of the instance (in UTC milliseconds)
18259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end the end time of the instance (in UTC milliseconds)
18269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param local a Time object with the timezone set to the local timezone
18279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values a map that will contain the timezone-dependent fields
18289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
18299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void computeTimezoneDependentFields(long begin, long end,
18309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local, ContentValues values) {
18319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(begin);
18329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startDay = Time.getJulianDay(begin, local.gmtoff);
18339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startMinute = local.hour * 60 + local.minute;
18349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(end);
18369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endDay = Time.getJulianDay(end, local.gmtoff);
18379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endMinute = local.hour * 60 + local.minute;
18389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Special case for midnight, which has endMinute == 0.  Change
18409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that to +24 hours on the previous day to make everything simpler.
18419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Exception: if start and end minute are both 0 on the same day,
18429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then leave endMinute alone.
18439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (endMinute == 0 && endDay > startDay) {
18449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endMinute = 24 * 60;
18459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endDay -= 1;
18469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_DAY, startDay);
18499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_DAY, endDay);
18509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_MINUTE, startMinute);
18519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_MINUTE, endMinute);
18529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
18559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
18569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
18579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
18589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
18599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
18609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
18619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
18629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
18639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
18649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
18659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
18669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
18679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
18689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
18699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
18709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
18719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
18729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
18739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
18746db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
18759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
187648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
187748587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1878315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1879315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
18809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
18819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
18829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1885fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalEvent) {
1886fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (!TextUtils.isEmpty(rrule)||
1887fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                !TextUtils.isEmpty(rdate)||
1888fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                !TextUtils.isEmpty(originalEvent));
18899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1891646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1892646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1893646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1894646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1895646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * corrects the fields DTSTART, DTEND, and DURATION if necessary. Also checks to ensure that
1896646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * either both DTSTART and DTEND or DTSTART and DURATION are set for each event.
1897646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1898646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @param updatedValues The values to check and correct
1899646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1900646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1901646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private boolean fixAllDayTime(Uri uri, ContentValues updatedValues) {
1902646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1903646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        if (updatedValues.containsKey(Events.ALL_DAY)
1904646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                && updatedValues.getAsInteger(Events.ALL_DAY).intValue() == 1) {
1905646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtstart = updatedValues.getAsLong(Events.DTSTART);
1906646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtend = updatedValues.getAsLong(Events.DTEND);
1907646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String duration = updatedValues.getAsString(Events.DURATION);
1908646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Time time = new Time();
1909646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Cursor currentTimesCursor = null;
1910646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String tempValue;
1911646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If a complete set of time fields doesn't exist query the db for them. A complete set
1912646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // is dtstart and dtend for non-recurring events or dtstart and duration for recurring
1913646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // events.
1914646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if(dtstart == null || (dtend == null && duration == null)) {
1915646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we have an id to search for, if not this is probably a new event
1916646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 2) {
1917646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    currentTimesCursor = query(uri,
1918646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            ALLDAY_TIME_PROJECTION,
1919646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selection */,
1920646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selectionArgs */,
1921646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* sort */);
1922646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    if (currentTimesCursor != null) {
1923646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (!currentTimesCursor.moveToFirst() ||
1924646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                                currentTimesCursor.getCount() != 1) {
1925646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // Either this is a new event or the query is too general to get data
1926646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // from the db. In either case don't try to use the query and catch
1927646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // errors when trying to update the time fields.
1928646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor.close();
1929646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor = null;
1930646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
1931646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1932646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1933646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1934646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1935646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // Ensure dtstart exists for this event (always required) and set so h,m,s are 0 if
1936646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // necessary.
1937646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // TODO Move this somewhere to check all events, not just allDay events.
1938646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtstart == null) {
1939646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (currentTimesCursor != null) {
1940646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // getLong returns 0 for empty fields, we'd like to know if a field is empty
1941646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // so getString is used instead.
1942646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    tempValue = currentTimesCursor.getString(ALLDAY_DTSTART_INDEX);
1943646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    try {
1944646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        dtstart = Long.valueOf(tempValue);
1945646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    } catch (NumberFormatException e) {
1946646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        currentTimesCursor.close();
1947646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        throw new IllegalArgumentException("Event has no DTSTART field, the db " +
1948646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            "may be damaged. Set DTSTART for this event to fix.");
1949646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1950646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
1951646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    throw new IllegalArgumentException("DTSTART cannot be empty for new events.");
1952646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1953646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1954646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1955646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.set(dtstart.longValue());
1956646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1957646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1958646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1959646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1960646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                updatedValues.put(Events.DTSTART, time.toMillis(true));
1961646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1962646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1963646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1964646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If dtend exists for this event make sure it's h,m,s are 0.
1965646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend == null && currentTimesCursor != null) {
1966646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // getLong returns 0 for empty fields. We'd like to know if a field is empty
1967646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // so getString is used instead.
1968646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                tempValue = currentTimesCursor.getString(ALLDAY_DTEND_INDEX);
1969646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                try {
1970646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = Long.valueOf(tempValue);
1971646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } catch (NumberFormatException e) {
1972646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = null;
1973646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1974646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1975646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend != null) {
1976646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.clear(Time.TIMEZONE_UTC);
1977646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.set(dtend.longValue());
1978646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1979646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.hour = 0;
1980646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.minute = 0;
1981646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.second = 0;
1982646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = time.toMillis(true);
1983646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DTEND, dtend);
1984646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1985646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1986646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1987646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1988646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (currentTimesCursor != null) {
1989646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (duration == null) {
1990646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = currentTimesCursor.getString(ALLDAY_DURATION_INDEX);
1991646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1992646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                currentTimesCursor.close();
1993646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1994646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1995646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration != null) {
1996646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                int len = duration.length();
1997646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1998646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 * in the seconds format, and if so converts it to days.
1999646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 */
2000646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (len == 0) {
2001646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = null;
2002646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else if (duration.charAt(0) == 'P' &&
2003646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        duration.charAt(len - 1) == 'S') {
2004646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int seconds = Integer.parseInt(duration.substring(1, len - 1));
2005646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
2006646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = "P" + days + "D";
2007646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DURATION, duration);
2008646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
2009646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
2010646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
2011646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
2012646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration == null && dtend == null) {
2013646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
2014646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        "an event.");
2015646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
2016646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
2017646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
2018646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
2019646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
20209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2021b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
2022ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
20239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
20249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
20279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2028b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final int match = sUriMatcher.match(uri);
20299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
20309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff              case SYNCSTATE:
20319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
20329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
20347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
20367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
20389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
20399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
2041e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
2042e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                validateEventData(updatedValues);
2043e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
2044e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
20609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2061646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(uri, updatedValues)) {
2062f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2063f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
2064f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
2065f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
2066646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
2067c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, true /* new event */, mDb);
20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
20749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
20759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
20769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
20819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
20828ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // if the Event Timezone is defined, store it as the original one in the
20838ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // ExtendedProperties table
20848ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    if (values.containsKey(Events.EVENT_TIMEZONE) && !callerIsSyncAdapter) {
20858ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        String originalTimezone = values.getAsString(Events.EVENT_TIMEZONE);
20868ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20878ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        ContentValues expropsValues = new ContentValues();
20888ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.EVENT_ID, id);
20898ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.NAME,
20908ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                EXT_PROP_ORIGINAL_TIMEZONE);
20918ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.VALUE, originalTimezone);
20928ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20938ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        // Insert the extended property
20948ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        long exPropId = mDbHelper.extendedPropertiesInsert(expropsValues);
20958ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        if (exPropId == -1) {
20968ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
20978ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                Log.e(TAG, "Cannot add the original Timezone in the "
20988ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                        + "ExtendedProperties table for Event: " + id);
20998ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
21008ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        } else {
21018ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            // Update the Event for saying it has some extended properties
21028ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            ContentValues eventValues = new ContentValues();
21038ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            eventValues.put(Events.HAS_EXTENDED_PROPERTIES, "1");
2104b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            int result = mDb.update("Events", eventValues, SQL_WHERE_ID,
21058ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    new String[] {String.valueOf(id)});
21068ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (result <= 0) {
21078ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                if (Log.isLoggable(TAG, Log.ERROR)) {
21088ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    Log.e(TAG, "Cannot update hasExtendedProperties column"
21098ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                            + " for Event: " + id);
21108ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                }
21118ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
21128ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        }
21138ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    }
2114dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
21159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
21209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountName = values.getAsString(Calendars._SYNC_ACCOUNT);
21219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Calendars._SYNC_ACCOUNT_TYPE);
21239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
21241b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    String eventsUrl = values.getAsString(Calendars.SYNC1);
21251b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
21269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2128dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
21299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
21339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.attendeesInsert(values);
21367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Attendees.EVENT_ID));
21387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
21449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
21459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
21469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.remindersInsert(values);
21497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Reminders.EVENT_ID));
21517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
21549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
21559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
21569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
21589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
21609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
21619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
21629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
21652fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
21662fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
21679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Calendar.ExtendedProperties.EVENT_ID)) {
21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
21719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.extendedPropertiesInsert(values);
21747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Calendar.ExtendedProperties.EVENT_ID));
21767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
21819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
21849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
21856db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2186315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
21877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
21889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
21899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
21909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
21939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
21949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
21979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
21989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2199e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2200e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Do some validation on event data before inserting.
2201e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * In particular make sure dtend, duration, etc make sense for
2202e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * the type of event (regular, recurrence, exception).  Remove
2203e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * any unexpected fields.
2204e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
2205e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues to insert
2206e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2207e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private void validateEventData(ContentValues values) {
2208e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2209e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2210e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2211e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2212e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT));
2213e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2214e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2215e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2216e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2217e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2218e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2219e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is the recurrence rule
2220e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2221e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2222e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2223e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
2224e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2225e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence: " + values);
2226e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2227e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2228e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_EVENT);
2229e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
2230e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2231e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2232e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2233e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2234e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2235e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2236e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2237e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2238e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2239e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2240e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
2241e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2242e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence exception: " + values);
2243e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2244e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2245e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2246e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2247e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2248e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2249e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2250e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2251e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2252e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2253e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2254e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2255e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
2256e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2257e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for event: " + values);
2258e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2259e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2260e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2261e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2262e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2263e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
22647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private void setEventDirty(int eventId) {
2265b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Integer[] {eventId});
22667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
22677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
22689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2274f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2275f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2276f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2277f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2278f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2279f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2290f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2291f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2292f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
22989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
23109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
23119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
23139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
23159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
23169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
23429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
23479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
23489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
23509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
23519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2352f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2353f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2354f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
23609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
23669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
23679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
23709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
23719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2374f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2375f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2376f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
23839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
23979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
23989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
23999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
24009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
24019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
24059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
24069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
24079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
24089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
24099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
24139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2418b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2419b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the instances table when an event is added or updated.
24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
24259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param newEvent true if the event is new.
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateInstancesLocked(ContentValues values,
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean newEvent,
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If there are no expanded Instances, then return.
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (fields.maxInstance == 0) {
24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
24389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
24419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis == null) {
24429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (newEvent) {
24439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // must be present for a new event.
24449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART missing.");
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2446f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
2447f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Missing DTSTART.  No need to update instance.");
2448f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
24549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!newEvent) {
24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Want to do this for regular event, recurrence, or exception.
24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // For recurrence or exception, more deletion may happen below if we
24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // do an instance expansion.  This deletion will suffice if the exception
24599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // is moved outside the window, for instance.
2460b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            db.delete(Tables.INSTANCES, Instances.EVENT_ID + "=?",
2461b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    new String[] {String.valueOf(rowId)});
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2464fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        String rrule = values.getAsString(Events.RRULE);
2465fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        String rdate = values.getAsString(Events.RDATE);
2466fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
2467fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isRecurrenceEvent(rrule, rdate, originalEvent))  {
24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // The recurrence or exception needs to be (re-)expanded if:
24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // a) Exception or recurrence that falls inside window
24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean insideWindow = dtstartMillis <= fields.maxInstance &&
24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    (lastDateMillis == null || lastDateMillis >= fields.minInstance);
24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // b) Exception that affects instance inside window
24739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // These conditions match the query in getEntries
24749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            //  See getEntries comment for explanation of subtracting 1 week.
24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean affectsWindow = originalInstanceTime != null &&
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime <= fields.maxInstance &&
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (insideWindow || affectsWindow) {
24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateRecurrenceInstancesLocked(values, rowId, db);
24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: an exception creation or update could be optimized by
24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // updating just the affected instances, instead of regenerating
24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the recurrence.
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis == null) {
24899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            dtendMillis = dtstartMillis;
24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // if the event is in the expanded range, insert
24939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // into the instances table.
24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: deal with durations.  currently, durations are only used in
24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences.
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentValues instanceValues = new ContentValues();
24999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.EVENT_ID, rowId);
25009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.BEGIN, dtstartMillis);
25019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.END, dtendMillis);
25029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean allDay = false;
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                allDay = allDayInteger != 0;
25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Update the timezone-dependent fields.
25109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local = new Time();
25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDay) {
25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = Time.TIMEZONE_UTC;
25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = fields.timezone;
25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
25189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.instancesInsert(instanceValues);
25199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
25239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Determines the recurrence entries associated with a particular recurrence.
25249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This set is the base recurrence and any exception.
25259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Normally the entries are indicated by the sync id of the base recurrence
25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * (which is the originalEvent in the exceptions).
25289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * However, a complication is that a recurrence may not yet have a sync id.
25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * In that case, the recurrence is specified by the rowId.
25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
25319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param recurrenceSyncId The sync id of the base recurrence, or null.
25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The row id of the base recurrence.
25339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return the relevant entries.
25349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
25359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) {
25369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
25379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
25399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
2540636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        String selectionArgs[];
25419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
2542b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            String where = SQL_WHERE_ID;
25439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2544636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {String.valueOf(rowId)};
25459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
2546b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            String where = Events._SYNC_ID + "=? OR " + Events.ORIGINAL_EVENT + "=?";
25479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2548636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId};
25499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
25519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Retrieving events to expand: " + qb.toString());
25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2554636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs,
25557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* groupBy */, null /* having */, null /* sortOrder */);
25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Do incremental Instances update of a recurrence or recurrence exception.
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
25619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method does performInstanceExpansion on just the modified recurrence,
25629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid the overhead of recomputing the entire instance table.
25639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
25649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
25659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
25669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
25679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateRecurrenceInstancesLocked(ContentValues values,
25699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
25709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
25719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
2572315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
25739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
2574315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String recurrenceSyncId;
25759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalEvent != null) {
25769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = originalEvent;
25779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
25789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the recurrence's sync id from the database
2579b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            recurrenceSyncId = DatabaseUtils.stringForQuery(db,
2580b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    SQL_SELECT_EVENTS_SYNC_ID, new String[] {String.valueOf(rowId)});
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrenceSyncId is the _sync_id of the underlying recurrence
25839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the recurrence hasn't gone to the server, it will be null.
25849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Need to clear out old instances
25869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
25879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating updating a recurrence that hasn't gone to the server.
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Need to delete based on row id
2589b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            String where = SQL_WHERE_ID_FROM_INSTANCES_NOT_SYNCED;
2590b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            db.delete(Tables.INSTANCES, where, new String[]{"" + rowId});
25919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
25929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating or modifying a recurrence or exception.
25939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Delete instances for recurrence (_sync_id = recurrenceSyncId)
25949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // and all exceptions (originalEvent = recurrenceSyncId)
2595b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            String where = SQL_WHERE_ID_FROM_INSTANCES_SYNCED;
2596b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            db.delete(Tables.INSTANCES, where, new String[]{recurrenceSyncId, recurrenceSyncId});
25979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do instance expansion
26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId);
26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
2602315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            performInstanceExpansion(fields.minInstance, fields.maxInstance, instancesTimezone,
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                     entries);
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
26059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
26069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
26079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
26129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
26139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
26149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
26159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
26169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
26179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
26189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
26199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
26209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
26219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
26229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
26239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
26259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
26279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
26289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
26379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
26389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2645f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2646f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2647f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2648f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2649f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2650f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2651f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            values.get(Calendar.Events.RRULE), e);
2652f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2653f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2654f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2656f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
26589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
26599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
26619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
26649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
26659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
26679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
26819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
26829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2686e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2687e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
2688e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues (in/out)
2689e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2690e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2691e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
26929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2701f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2702f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2703f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
27049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
27059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
27099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2711b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        rawValues.put(Calendar.EventsRawTimes.EVENT_ID, eventId);
27129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
2731b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            rawValues.put(Calendar.EventsRawTimesColumns.DTSTART_2445, time.format2445());
27329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
27359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
27369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
2737b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            rawValues.put(Calendar.EventsRawTimesColumns.DTEND_2445, time.format2445());
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
27439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
27449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
27459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
27469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
27479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
2750b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            rawValues.put(Calendar.EventsRawTimesColumns.ORIGINAL_INSTANCE_TIME_2445,
2751b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
27529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
27579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
2758b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            rawValues.put(Calendar.EventsRawTimesColumns.LAST_DATE_2445, time.format2445());
27599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
27629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2765b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2766b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2767ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
27689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
27699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
27729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
27739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
2776dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
27779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
27789323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2779dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2780dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2781dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2782dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27841ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
27871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                selection = appendAccountToSelection(uri, selection);
27887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
27891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
2790b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                Cursor cursor = mDb.query(Tables.EVENTS, ID_ONLY_PROJECTION,
27911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
27927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
27939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
27941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
27951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
279610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
27979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
279810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                    scheduleNextAlarm(false /* do not remove alarms */);
2799dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
28009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
28019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
28029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
28039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
28059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
28071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
28081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
28091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
28101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
28111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
28121ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
28131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
281410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
28151ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
28169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
28179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2819b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
28207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2821b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, selection, selectionArgs);
28227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
28259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28282fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2831b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
2832b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
28337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2834b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, null /* selection */,
28352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2841b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, selection, selectionArgs);
28427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2843b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, selection, selectionArgs);
28447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28502fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2853b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, SQL_WHERE_ID,
2854b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
28557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2856b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, null /* selection */,
28572fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28582fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
28602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
28612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
28622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
2863b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
28642fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2865b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, selection,
2866b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
28672fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28682fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
28692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
28702fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
28712fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28722fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28742fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
28752fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
2876b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
2877636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
28782fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2879b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, null /* selection */,
28802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
28849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2886b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
28877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2888b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.CALENDAR_ALERTS, uri, selection, selectionArgs);
28897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
28929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28952fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28962fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
28972fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
28989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2899b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
2900b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
29037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
29049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
2905b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                StringBuilder selectionSb = new StringBuilder(BaseColumns._ID + "=");
29069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
29079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
29089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
29099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
29119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
29139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
2915595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
29167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return deleteMatchingCalendars(selection); // TODO: handle in sync adapter
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
29196db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2920315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
29229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
292710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
29281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
2929192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
29301ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
2932b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
2933b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
2934636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
29351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
29361ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
29371ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
29381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
29391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
294048f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
29411ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
29431ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
29441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
29451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
29461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
29471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX);
2948fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isRecurrenceEvent(rrule, rdate, origEvent)) {
29491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
29501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
29511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
295248f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
295348f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
295448f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
2955b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
29561ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
29571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
29581b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
29591ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
2960b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
296102494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
296243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
296343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
2964b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
2965b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
2966b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
2967b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
2968b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
2969b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
29701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
29711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
29721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
29731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
29741ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
29751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
29768f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
297710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
297810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio            scheduleNextAlarm(false /* do not remove alarms */);
2979dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
298010b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
29811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
29821ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
29831ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
29857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
29867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
29877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
29887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
29897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
29907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
29917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
29927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
29937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
29947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
29957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues values = new ContentValues();
29967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        values.put(Events._SYNC_DIRTY, "1");
29977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
29987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
29997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
30007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
30017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
3002b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.delete(table, SQL_WHERE_ID, new String[] {String.valueOf(id)});
3003b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
3004b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
30057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
30067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
30077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
30087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
30097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
30107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
30117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
30127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
30137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
30147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
30157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
30167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
30177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
30187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
30197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
30207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
30217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
30227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
30237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
30247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
30257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
30267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues dirtyValues = new ContentValues();
30277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        dirtyValues.put(Events._SYNC_DIRTY, "1");
30287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
30297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
30307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
30317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
30327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
3033b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(table, values, SQL_WHERE_ID, new String[] {String.valueOf(id)});
3034b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
3035b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
30367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
30377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
30387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
30397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
30407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
30417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
30427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
30437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
30449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private int deleteMatchingCalendars(String where) {
30459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
30469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
30479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
30489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3049b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, where,
30507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* selectionArgs */, null /* groupBy */,
30517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
30529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
30539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
30549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
30569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
30589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
30619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
30629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3063b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        return mDb.delete(Tables.CALENDARS, where, null /* whereArgs */);
30649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3066fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private Cursor getCursorForEventIdAndProjection(String eventId, String[] projection) {
3067fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return mDb.query(Tables.EVENTS,
3068fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                projection,
3069fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                SQL_WHERE_ID,
3070fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { eventId },
3071fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* group by */,
3072fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* having */,
3073fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* order by*/);
3074fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3075fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3076fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
3077fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
3078fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3079fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
3080fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3081fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
3082fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3083fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
3084fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
3085fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
3086fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3087fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3088fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
3089fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
3090fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3091fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
3092fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
3093fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
3094fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3095fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
3096fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
3097fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
3098fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesStatusCancelUpdateMeanUpdate(String eventId, ContentValues values) {
3099fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        boolean isStatusCanceled = values.containsKey(Events.STATUS) &&
3100fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
3101fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
3102fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            Cursor cursor = null;
3103fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            try {
3104fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                cursor = getCursorForEventIdAndProjection(eventId,
3105fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        new String[] { Events.RRULE, Events.RDATE, Events.ORIGINAL_EVENT });
3106fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (!cursor.moveToFirst()) {
3107fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3108fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        Log.w(TAG, "Cannot find Event with id: " + eventId);
3109fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3110fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    return false;
3111fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3112fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String rrule = cursor.getString(0);
3113fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String rdate = cursor.getString(1);
3114fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String originalEvent = cursor.getString(2);
3115fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3116fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                boolean isRecurrenceException =
3117fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        isRecurrenceEvent(rrule, rdate, originalEvent) &&
3118fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        !TextUtils.isEmpty(originalEvent);
3119fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3120fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isRecurrenceException) {
3121fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    return doesEventExistForSyncId(originalEvent);
3122fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3123fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            } finally {
3124fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                cursor.close();
3125fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3126fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3127fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        // This is the normal case, we just want an UPDATE
3128fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return true;
3129fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3130fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
31319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: call calculateLastDate()!
31329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
31339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
3134b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
3135ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
31369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
31379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
31409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
31419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
314343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS
3144315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                && match != EVENTS && match != CALENDARS && match != PROVIDER_PROPERTIES) {
3145b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            throw new IllegalArgumentException("WHERE based updates not supported");
31469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3147fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
31489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
31499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
31509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
31519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
31529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
31549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
3155dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
3156dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
31579323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3158dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3159dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3160dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
31619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
316343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
31649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
31659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
316643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
316743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
316843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null) {
316943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        throw new UnsupportedOperationException("Selection not permitted for "
317043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                                + uri);
317143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
317243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
317343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
317443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
317543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
317643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
317743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
317843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
317943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null && selection.startsWith("_id=")) {
318043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
318143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
318243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
318343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
3184b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
318543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
318643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
318743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
318843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    values.put(Calendars._SYNC_DIRTY, 1);
31892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
31919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
31929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
31939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3195b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
3196636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
31979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31983ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
3199d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
3200d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    if (values.containsKey(Calendars.SELECTED)) {
3201d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
3202d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
3203d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
3204d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
3205d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        scheduleNextAlarm(false);
3206d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
32073ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
3208dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
32093ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
32103ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
32119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
32129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
32137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
32149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
32159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
32167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = 0;
32177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (match == EVENTS_ID) {
32187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    id = ContentUris.parseId(uri);
3219a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                } else if (callerIsSyncAdapter) {
322043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: same remark as for CALENDARS/CALENDARS_ID case as this is not
322143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // sufficient to deal with all the "_id" case in selection
3222a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    if (selection != null && selection.startsWith("_id=")) {
32237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // The ContentProviderOperation generates an _id=n string instead of
32247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // adding the id to the URL, so parse that out here.
32257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        id = Long.parseLong(selection.substring(4));
3226a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    } else {
3227a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // Sync adapter Events operation affects just Events table, not associated
3228a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // tables.
3229646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (fixAllDayTime(uri, values)) {
3230f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.WARN)) {
3231f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                Log.w(TAG, "updateInTransaction: Caller is sync adapter. " +
3232f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                        "allDay is true but sec, min, hour were not 0.");
3233f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            }
3234646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
3235a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        return mDb.update("Events", values, selection, selectionArgs);
3236a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    }
32377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3238a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    throw new IllegalArgumentException("Unknown URL " + uri);
32397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
32407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
32417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
32427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
32439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Disallow updating the attendee status in the Events
32449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // table.  In the future, we could support this but we
32459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // would have to query and update the attendees table
32469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // to keep the values consistent.
32479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
32489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
32499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.SELF_ATTENDEE_STATUS
32509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
32519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
32529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                // TODO: should we allow this?
32547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (values.containsKey(Events.HTML_URI) && !callerIsSyncAdapter) {
32559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
32569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.HTML_URI
32579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
32589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3259fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String strId = String.valueOf(id);
3260fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                // For taking care about recurrences exceptions cancelations, check if this needs
3261fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                //  to be an UPDATE or a DELETE
3262fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                boolean isUpdate = doesStatusCancelUpdateMeanUpdate(strId, values);
3263e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
3264e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // TODO: should extend validateEventData to work with updates and call it here
3265e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
32669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
3267f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3268f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "Could not update event.");
3269f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
32709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return 0;
32719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3272646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we pass in a uri with the id appended to fixAllDayTime
3273646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                Uri allDayUri;
3274646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 1) {
3275646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = ContentUris.withAppendedId(uri, id);
3276646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
3277646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = uri;
3278646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
3279646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(allDayUri, updatedValues)) {
3280f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3281f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "updateInTransaction: " +
3282f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
3283f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
3284646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
32859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3286fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                int result;
32879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3288fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isUpdate) {
3289fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    result = mDb.update(Tables.EVENTS, updatedValues, SQL_WHERE_ID,
3290fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            new String[] { strId });
3291fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    if (result > 0) {
3292fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        updateEventRawTimesLocked(id, updatedValues);
3293fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb);
3294fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3295fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        if (values.containsKey(Events.DTSTART)) {
3296fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            // The start time of the event changed, so run the
3297fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            // event alarm scheduler.
3298fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            if (Log.isLoggable(TAG, Log.DEBUG)) {
3299fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                                Log.d(TAG, "updateInternal() changing event");
3300fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            }
3301fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            scheduleNextAlarm(false /* do not remove alarms */);
33029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
33033ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3304fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        sendUpdateNotification(id, callerIsSyncAdapter);
3305fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3306fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                } else {
3307fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    result = deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3308fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    scheduleNextAlarm(false /* do not remove alarms */);
3309fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    sendUpdateNotification(callerIsSyncAdapter);
33109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3311fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
33129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
33139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
33152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
33162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
33172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
33189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
33199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
33209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
33227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3323b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.ATTENDEES, values, SQL_WHERE_ID,
332483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
33257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3326b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.ATTENDEES, values, uri, null /* selection */,
33272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
33287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
33299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33302fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
33312fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
33322fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
33332fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
33342fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
33352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
33369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3337b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
3338636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
33399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
33412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
33422fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
3343b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
33449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
33462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
33472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
33482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
33497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
33507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3351b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = mDb.update(Tables.REMINDERS, values, SQL_WHERE_ID,
335283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
33537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3354b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = updateInTable(Tables.REMINDERS, values, uri, null /* selection */,
33552fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
33567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
33577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
33589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
33599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
33609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
33619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
33629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
33647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
33659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33662fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
33672fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
33682fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
33692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
33707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
33717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3372b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.EXTENDED_PROPERTIES, values, SQL_WHERE_ID,
3373636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
33747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3375b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.EXTENDED_PROPERTIES, values, uri,
3376b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            null /* selection */, null /* selectionArgs */);
33777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
33789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
337983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
338083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
338183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
338283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(false);
338383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
338483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
338583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
338683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(true);
338783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
338883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
33899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3390315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
3391315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (selection == null) {
3392315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection cannot be null for " + uri);
3393315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3394315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
3395315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
3396315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3397315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3398315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
3399315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3400315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
3401315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
3402315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
3403315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3404315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3405315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
3406315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
3407315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3408315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
3409315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
3410b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
3411315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3412315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
3413315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone type is set to "home", set the Instances timezone to the previous
3414315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone type is set to "auto", set the Instances timezone to the current
3415315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                //      device one
3416315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone Instances is set AND if we are in "home" timezone type, then
3417315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                //      save the timezone Instance into "previous" too
3418315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
3419315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
3420315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
3421315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
3422315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
3423315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
3424315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
3425315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
3426315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
3427315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
3428315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
3429315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3430315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
3431d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
3432315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
3433315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3434d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3435315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3436315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3437315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
3438315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
3439315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
3440315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
3441315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
3442315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3443d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3444315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3445315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3446315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3447315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3448315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
3449315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
3450315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
3451315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
3452315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
3453315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
3454315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
3455315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
3456d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
3457315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
3458315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
3459315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
3460d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
3461315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3462315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3463315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3464315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3465315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
3466315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
3467315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
34689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
34699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
34709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3473595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) {
3474b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3475b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_NAME);
3476b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3477b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_TYPE);
3478595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
3479595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere(Calendar.Calendars._SYNC_ACCOUNT + "="
3480595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3481595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
3482595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
3483595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
3484595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere("1"); // I.e. always true
3485595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3486595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3487595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
34889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
3489b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3490b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_NAME);
3491b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3492b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_TYPE);
34939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
34949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            StringBuilder selectionSb = new StringBuilder(Calendar.Calendars._SYNC_ACCOUNT + "="
34959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
34969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
34979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
34989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!TextUtils.isEmpty(selection)) {
34999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(" AND (");
35009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(selection);
35019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(')');
35029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
35039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selectionSb.toString();
35049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
35059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
35069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
35109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
35119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
35129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
35139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE,
35141b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                        Calendars.SYNC1, Calendars.SYNC_EVENTS},
35159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
35169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
35189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
35209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
35219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
3522ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
35239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3524ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
3525ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
3526ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
3527ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
3528ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
3529ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
3530ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
35319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
35329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
35339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
35349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35369535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
35379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
3538f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3539f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
3540f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
3541f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
35429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
35439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35459535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
35469535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
35479535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
35489535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
35499535627bf6295cd94447beb83e1aac41f50c3600Erik        }
35509535627bf6295cd94447beb83e1aac41f50c3600Erik
35519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
35529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
35539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
35549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
35579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
35589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
35599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarmCheck(long triggerTime) {
35629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Intent intent = new Intent(CalendarReceiver.SCHEDULE);
3563e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        intent.setClass(mContext, CalendarReceiver.class);
3564e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        PendingIntent pending = PendingIntent.getBroadcast(mContext,
35659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_NO_CREATE);
35669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (pending != null) {
35679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Cancel any previous alarms that do the same thing.
3568e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            getOrCreateCalendarAlarmManager().cancel(pending);
35699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3570e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        pending = PendingIntent.getBroadcast(mContext,
35719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
35729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
35749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
35759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(triggerTime);
35769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
35779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
35789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3580e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        getOrCreateCalendarAlarmManager().set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
35819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarm(boolean removeAlarms) {
35848bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        // We aggregate first the "remove alarm flag". Whenever it is to true, it will be sticky
35858bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mNeedRemoveAlarms.set(mNeedRemoveAlarms.get() || removeAlarms);
35868bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        if (!mNextAlarmCheckScheduled.getAndSet(true)) {
35878bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.DEBUG)) {
35888bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio                Log.d(TAG, "Scheduling check of next Alarm");
35898bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            }
35908bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            Intent intent = new Intent(ACTION_CHECK_NEXT_ALARM);
35918bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            intent.putExtra(REMOVE_ALARM_VALUE, removeAlarms);
35928bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            PendingIntent pending = PendingIntent.getBroadcast(mContext,
35938bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio                    0 /* ignored */, intent, PendingIntent.FLAG_NO_CREATE);
35948bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            if (pending != null) {
35958bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio                // Cancel any previous Alarm check requests
3596e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                getOrCreateCalendarAlarmManager().cancel(pending);
35978bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            }
35988bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            pending = PendingIntent.getBroadcast(mContext,
35998bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio                    0 /* ignored */, intent, PendingIntent.FLAG_CANCEL_CURRENT);
36008bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
36018bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio            // Trigger the check in 5s from now
3602e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            long triggerAtTime = SystemClock.elapsedRealtime() + ALARM_CHECK_DELAY_MILLIS;
3603e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            getOrCreateCalendarAlarmManager().set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
3604e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                    triggerAtTime, pending);
36058bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        }
36069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
36079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
36099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread and schedules an alarm for
36109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the next calendar event, if necessary.
36119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
36128bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    void runScheduleNextAlarm(boolean removeAlarms) {
36138bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        // Reset so that we can accept other schedules of next alarm
36148bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mNextAlarmCheckScheduled.set(false);
3615e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mDb.beginTransaction();
36169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
36179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (removeAlarms) {
3618e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                removeScheduledAlarmsLocked(mDb);
36199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
3620e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            scheduleNextAlarmLocked(mDb);
3621e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb.setTransactionSuccessful();
36229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
3623e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb.endTransaction();
36249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
36269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
36289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method looks at the 24-hour window from now for any events that it
36299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * needs to schedule.  This method runs within a database transaction. It
36309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * also runs in a background thread.
36319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
36329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The CalendarProvider2 keeps track of which alarms it has already scheduled
36339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid scheduling them more than once and for debugging problems with
36349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms.  It stores this knowledge in a database table called CalendarAlerts
36359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * which persists across reboots.  But the actual alarm list is in memory
36369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and disappears if the phone loses power.  To avoid missing an alarm, we
36379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * clear the entries in the CalendarAlerts table when we start up the
36389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * CalendarProvider2.
36399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
36409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Scheduling an alarm multiple times is not tragic -- we filter out the
36419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * extra ones when we receive them. But we still need to keep track of the
36429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled alarms. The main reason is that we need to prevent multiple
36439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * notifications for the same alarm (on the receive side) in case we
36449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * accidentally schedule the same alarm multiple times.  We don't have
36459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * visibility into the system's alarm list so we can never know for sure if
36469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we have already scheduled an alarm and it's better to err on scheduling
36479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * an alarm twice rather than missing an alarm.  Another reason we keep
36489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * track of scheduled alarms in a database table is that it makes it easy to
36499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * run an SQL query to find the next reminder that we haven't scheduled.
36509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
36519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
36529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
36539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void scheduleNextAlarmLocked(SQLiteDatabase db) {
3654b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        Time time = new Time();
36559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long currentMillis = System.currentTimeMillis();
36579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long start = currentMillis - SCHEDULE_ALARM_SLACK;
36589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long end = start + (24 * 60 * 60 * 1000);
36599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
36609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(start);
36619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
36629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
36639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36658f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Delete rows in CalendarAlert where the corresponding Instance or
36668f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Reminder no longer exist.
36678f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Also clear old alarms but keep alarms around for a while to prevent
36689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // multiple alerts for the same reminder.  The "clearUpToTime'
36699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // should be further in the past than the point in time where
36709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we start searching for events (the "start" variable defined above).
36718f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        String selectArg[] = new String[] {
36728f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            Long.toString(currentMillis - CLEAR_OLD_ALARM_THRESHOLD)
36738f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        };
36748f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
36758f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        int rowsDeleted =
36768f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            db.delete(CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg);
36779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long nextAlarmTime = end;
3679e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(mContentResolver, currentMillis);
36808f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) {
36818f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            nextAlarmTime = tmpAlarmTime;
36829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Extract events from the database sorted by alarm time.  The
36859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // alarm times are computed from Instances.begin (whose units
36869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // are milliseconds) and Reminders.minutes (whose units are
36879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // minutes).
36889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
36899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events whose end time is already in the past.
36909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events alarms that we have already scheduled.
36919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
36929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 1: we can add support for the case where Reminders.minutes
36939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // equals -1 to mean use Calendars.minutes by adding a UNION for
36949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that case where the two halves restrict the WHERE clause on
36959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders.minutes != -1 and Reminders.minutes = 1, respectively.
36969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
36979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 2: we have to name "myAlarmTime" different from the
36989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // "alarmTime" column in CalendarAlerts because otherwise the
36999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query won't find multiple alarms for the same event.
3700156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        //
3701156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // The CAST is needed in the query because otherwise the expression
3702156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will be untyped and sqlite3's manifest typing will not convert the
3703156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // string query parameter to an int in myAlarmtime>=?, so the comparison
3704156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will fail.  This could be simplified if bug 2464440 is resolved.
3705b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3706b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        time.setToNow();
3707b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        time.normalize(false);
3708b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        long localOffset = time.gmtoff * 1000;
3709b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3710b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String allDayOffset = " -(" + localOffset + ") ";
3711b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String subQueryPrefix = "SELECT " + Instances.BEGIN;
3712b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String subQuerySuffix = " -(" + Reminders.MINUTES + "*" +
3713b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + DateUtils.MINUTE_IN_MILLIS + ")"
3714b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AS myAlarmTime" + "," + Tables.INSTANCES
3715d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "." + Instances.EVENT_ID + " AS eventId"
3716d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Instances.BEGIN + "," + Instances.END
3717d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Instances.TITLE + "," + Instances.ALL_DAY
3718d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Reminders.METHOD + "," + Reminders.MINUTES
3719d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " FROM " + Tables.INSTANCES
3720d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " INNER JOIN " + Views.EVENTS
3721d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ON (" + Views.EVENTS + "." + Events._ID
3722d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID + ")"
3723d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " INNER JOIN " + Tables.REMINDERS
3724d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ON (" + Tables.INSTANCES + "." + Instances.EVENT_ID
3725d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID + ")"
3726d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " WHERE " + Calendars.SELECTED + "=1"
3727156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime>=CAST(? AS INT)"
3728156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime<=CAST(? AS INT)"
3729d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " AND " + Instances.END + ">=?"
3730b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Reminders.METHOD + "=" + Reminders.METHOD_ALERT;
3731b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3732b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we query separately for all day events to convert to local time from UTC
3733b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we need to /subtract/ the offset to get the correct resulting local time
3734b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String allDayQuery = subQueryPrefix + allDayOffset + subQuerySuffix
3735b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Instances.ALL_DAY + "=1";
3736b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String nonAllDayQuery = subQueryPrefix + subQuerySuffix
3737b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Instances.ALL_DAY + "=0";
3738b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3739b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we use UNION ALL because we are guaranteed to have no dupes between
3740b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // the two queries, and it is less expensive
3741b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String query = "SELECT *"
3742b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " FROM (" + allDayQuery + " UNION ALL " + nonAllDayQuery + ")"
3743b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                // avoid rescheduling existing alarms
3744b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                + " WHERE 0=(SELECT count(*) FROM " + Tables.CALENDAR_ALERTS + " CA"
3745b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " WHERE CA." + CalendarAlerts.EVENT_ID + "=eventId"
3746b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " AND CA." + CalendarAlerts.BEGIN + "=" + Instances.BEGIN
3747b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " AND CA." + CalendarAlerts.ALARM_TIME + "=myAlarmTime)"
3748d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ORDER BY myAlarmTime," + Instances.BEGIN + "," + Instances.TITLE;
37499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3750b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String queryParams[] = new String[] { String.valueOf(start), String.valueOf(nextAlarmTime),
3751b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                String.valueOf(currentMillis), String.valueOf(start), String.valueOf(nextAlarmTime),
3752b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                String.valueOf(currentMillis) };
3753b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3754315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
3755315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean isHomeTimezone = mCalendarCache.readTimezoneType().equals(
3756315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                CalendarCache.TIMEZONE_TYPE_HOME);
3757b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // expand this range by a day on either end to account for all day events
3758b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        acquireInstanceRangeLocked(start - DateUtils.DAY_IN_MILLIS,
3759b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                end + DateUtils.DAY_IN_MILLIS,
3760d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                false /* don't use minimum expansion windows */,
3761315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances deletion and expansion */,
3762315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone,
3763315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                isHomeTimezone);
37649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
37659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
3766156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff            cursor = db.rawQuery(query, queryParams);
37679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37688f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int beginIndex = cursor.getColumnIndex(Instances.BEGIN);
37698f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int endIndex = cursor.getColumnIndex(Instances.END);
37708f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int eventIdIndex = cursor.getColumnIndex("eventId");
37718f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime");
37728f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES);
37739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
37759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.set(nextAlarmTime);
37769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
37778f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                Log.d(TAG, "cursor results: " + cursor.getCount() + " nextAlarmTime: "
37788f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        + alarmTimeStr);
37799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
37809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (cursor.moveToNext()) {
37829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule all alarms whose alarm time is as early as any
37839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // scheduled alarm.  For example, if the earliest alarm is at
37849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // 1pm, then we will schedule all alarms that occur at 1pm
37859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // but no alarms that occur later than 1pm.
37869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Actually, we allow alarms up to a minute later to also
37879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // be scheduled so that we don't have to check immediately
37889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // again after an event alarm goes off.
37898f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long alarmTime = cursor.getLong(alarmTimeIndex);
37908f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long eventId = cursor.getLong(eventIdIndex);
37918f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final int minutes = cursor.getInt(minutesIndex);
37928f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long startTime = cursor.getLong(beginIndex);
37938f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long endTime = cursor.getLong(endIndex);
37949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
37969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(alarmTime);
37979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
37989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(startTime);
37999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
38008f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
38018f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    Log.d(TAG, "  looking at id: " + eventId + " " + startTime + startTimeStr
38028f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                            + " alarm: " + alarmTime + schedTime);
38039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
38049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (alarmTime < nextAlarmTime) {
38069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    nextAlarmTime = alarmTime;
38079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else if (alarmTime >
38081edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff                           nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) {
38099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This event alarm (and all later ones) will be scheduled
38109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // later.
38118f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
38128f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        Log.d(TAG, "This event alarm (and all later ones) will be scheduled later");
38138f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    }
38149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    break;
38159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
38169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Avoid an SQLiteContraintException by checking if this alarm
38189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // already exists in the table.
3819e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                if (CalendarAlerts.alarmExists(mContentResolver, eventId, startTime, alarmTime)) {
38209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
38219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int titleIndex = cursor.getColumnIndex(Events.TITLE);
38229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        String title = cursor.getString(titleIndex);
38239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.d(TAG, "  alarm exists for id: " + eventId + " " + title);
38249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
38259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
38269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
38279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Insert this alarm into the CalendarAlerts table
3829e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                Uri uri = CalendarAlerts.insert(mContentResolver, eventId, startTime,
38309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        endTime, alarmTime, minutes);
38319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (uri == null) {
3832f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
3833f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "runScheduleNextAlarm() insert into "
3834f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "CalendarAlerts table failed");
3835f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
38369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
38379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
38389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3839e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio                getOrCreateCalendarAlarmManager().scheduleAlarm(alarmTime);
38409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
38429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
38439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
38449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38478f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Refresh notification bar
38488f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (rowsDeleted > 0) {
3849e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            getOrCreateCalendarAlarmManager().scheduleAlarm(currentMillis);
38508f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        }
38518f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
38529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If we scheduled an event alarm, then schedule the next alarm check
38539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for one minute past that alarm.  Otherwise, if there were no
38549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event alarms scheduled, then check again in 24 hours.  If a new
38559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event is inserted before the next alarm check, then this method
38569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // will be run again when the new event is inserted.
38579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (nextAlarmTime != Long.MAX_VALUE) {
38581edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS);
38599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
38601edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS);
38619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
38639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
38659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Removes the entries in the CalendarAlerts table for alarms that we have
38669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled but that have not fired yet. We do this to ensure that we
38679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * don't miss an alarm.  The CalendarAlerts table keeps track of the
38689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms that we have scheduled but the actual alarm list is in memory
38699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and will be cleared if the phone reboots.
38709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
38719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We don't need to remove entries that have already fired, and in fact
38729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we should not remove them because we need to display the notifications
38739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * until the user dismisses them.
38749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
38759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We could remove entries that have fired and been dismissed, but we leave
38769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * them around for a while because it makes it easier to debug problems.
38779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Entries that are old enough will be cleaned up later when we schedule
38789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * new alarms.
38799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
38809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void removeScheduledAlarmsLocked(SQLiteDatabase db) {
38819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
38829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "removing scheduled alarms");
38839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.delete(CalendarAlerts.TABLE_NAME,
38859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED, null /* whereArgs */);
38869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
38879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3888a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3889a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3890a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3891a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
3892dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
3893dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     * @param whether or not the update is being triggered by a sync
3894a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3895dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
3896dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
3897dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
3898a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3899a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3900a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3901a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3902a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3903a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
3904a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
3905a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
3906a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3907a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
3908a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3909a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * @param the ID of the event that changed, or -1 for no specific event
3910dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     * @param whether or not the update is being triggered by a sync
3911a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3912dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
3913dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
3914a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
3915a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
3916a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
3917a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
3918a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
3919dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
3920dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
3921dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
3922dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
3923dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
3924dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
3925dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
3926dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
3927dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
3928dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
3929dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
3930dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
3931dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
3932dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
3933dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
3934dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
3935dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
3936a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
3937dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
3938a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3939a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3940a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3941a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
3942a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
3943a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #sendUpdateNotification()} instead.
3944a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3945a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * @see #sendUpdateNotification()
3946a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3947a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
3948a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
3949dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                Calendar.CONTENT_URI);
3950f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
3951f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
3952f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
3953e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
3954a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3955a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
39569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
39579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
39589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
39599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int DELETED_EVENTS = 4;
39609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS = 5;
39619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_ID = 6;
39629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES = 7;
39639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES_ID = 8;
39649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS = 9;
39659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS_ID = 10;
39669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES = 11;
39679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES_ID = 12;
39689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS = 13;
39699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_ID = 14;
39709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_BY_INSTANCE = 15;
39716db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int INSTANCES_BY_DAY = 16;
39726db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE = 17;
39736db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE_ID = 18;
39746db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES = 19;
39756db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES_ID = 20;
39766db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_DAYS = 21;
397783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM = 22;
397883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM_REMOVE = 23;
397948587d3291c4db7f0942e1bff55b88cfa7764ba0Erik    private static final int TIME = 24;
398043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio    private static final int CALENDAR_ENTITIES = 25;
398143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio    private static final int CALENDAR_ENTITIES_ID = 26;
398281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final int INSTANCES_SEARCH = 27;
398381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final int INSTANCES_SEARCH_BY_DAY = 28;
3984315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final int PROVIDER_PROPERTIES = 29;
39859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
39879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
39889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sEventsProjectionMap;
398919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
39909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
39919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
39929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
3993315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
39949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
3996b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/when/*/*", INSTANCES);
3997b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
399881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
399981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/searchbyday/*/*/*",
400081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
4001b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
4002b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events", EVENTS);
4003b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events/#", EVENTS_ID);
4004b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities", EVENT_ENTITIES);
4005b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
4006b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars", CALENDARS);
4007b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars/#", CALENDARS_ID);
400843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
400943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
4010b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "deleted_events", DELETED_EVENTS);
4011b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees", ATTENDEES);
4012b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees/#", ATTENDEES_ID);
4013b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders", REMINDERS);
4014b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders/#", REMINDERS_ID);
4015b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
4016b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties/#", EXTENDED_PROPERTIES_ID);
4017b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
4018b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
4019b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/by_instance",
4020b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
4021c4e53191b570e09959c5723f4d253977ba48f2d0Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate", SYNCSTATE);
402283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
402383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_PATH, SCHEDULE_ALARM);
402483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
402548587d3291c4db7f0942e1bff55b88cfa7764ba0Erik        sUriMatcher.addURI(Calendar.AUTHORITY, "time/#", TIME);
4026997e2e5cb006682bc1a82441304994b458d9745dErik        sUriMatcher.addURI(Calendar.AUTHORITY, "time", TIME);
4027315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "properties", PROVIDER_PROPERTIES);
40289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
40309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
40319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HTML_URI, "htmlUri");
40329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TITLE, "title");
40339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
40349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DESCRIPTION, "description");
40359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.STATUS, "eventStatus");
40369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
40379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
40389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTSTART, "dtstart");
40399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTEND, "dtend");
40409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
40419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DURATION, "duration");
40429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ALL_DAY, "allDay");
40439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.VISIBILITY, "visibility");
40449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TRANSPARENCY, "transparency");
40459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
40469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
40479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RRULE, "rrule");
40489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RDATE, "rdate");
40499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXRULE, "exrule");
40509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXDATE, "exdate");
40519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
40529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
40539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
40549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.LAST_DATE, "lastDate");
40559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
40569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
40579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
40589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
40599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
40609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORGANIZER, "organizer");
40611b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventsProjectionMap.put(Events.DELETED, "deleted");
40629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4063e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
40641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
40651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
40661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
40679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
4068982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.COLOR, "color");
4069982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.ACCESS_LEVEL, "access_level");
4070982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.SELECTED, "selected");
40711b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventsProjectionMap.put(Calendars.SYNC1, "sync1");
40729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.TIMEZONE, "timezone");
40739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, "ownerAccount");
40749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4075982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
4076e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
4077e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
4078982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4079e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4080982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
40811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._ID, "_id");
40821ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ID, "_sync_id");
40831ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_VERSION, "_sync_version");
40841ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_TIME, "_sync_time");
4085c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventsProjectionMap.put(Events._SYNC_DATA, "_sync_local_id");
40861ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_DIRTY, "_sync_dirty");
40871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "_sync_account");
40889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE,
40891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                "_sync_account_type");
40909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
409146f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
409219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HTML_URI, "htmlUri");
409319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TITLE, "title");
409419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, "description");
409519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
409619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.STATUS, "eventStatus");
409719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
409819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
409919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTSTART, "dtstart");
410019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTEND, "dtend");
410119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DURATION, "duration");
410219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
410319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ALL_DAY, "allDay");
410419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.VISIBILITY, "visibility");
410519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TRANSPARENCY, "transparency");
410619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
410719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
410819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RRULE, "rrule");
410919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RDATE, "rdate");
411019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXRULE, "exrule");
411119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXDATE, "exdate");
411219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
411319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
411419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
411519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.LAST_DATE, "lastDate");
411619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
411719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
411819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
411919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
412019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
412119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORGANIZER, "organizer");
41221b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventEntitiesProjectionMap.put(Events.DELETED, "deleted");
412319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
412419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
4125c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventEntitiesProjectionMap.put(Events._SYNC_DATA, Events._SYNC_DATA);
412619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_VERSION, Events._SYNC_VERSION);
412719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_DIRTY, Events._SYNC_DIRTY);
41281b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventEntitiesProjectionMap.put(Calendars.SYNC1, Calendars.SYNC1);
4129c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio        sEventEntitiesProjectionMap.put(Events.SYNC_ADAPTER_DATA, Events.SYNC_ADAPTER_DATA);
413019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
41319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
41321b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
41339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
41349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
41359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
41369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
41379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
41389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
41399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
41409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
41419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
41439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
41449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
41459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
41469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
41479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
41489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
41499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
41509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
41529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
41539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
41549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
41559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
41569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
41589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
41599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
41609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
41619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
41629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
41639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
41649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4165315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4166315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4167315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4168315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4169315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
41709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
41739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make sure that there are no entries for accounts that no longer
41749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * exist. We are overriding this since we need to delete from the
41759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
41767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
41777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
41789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
41799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
4180ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4181ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
4182ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
4183ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4184ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4185ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
41869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
418746f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>();
418846f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
41899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
41909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
41919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
41929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
41939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
41949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
41969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
41979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            for (String table : new String[]{Tables.CALENDARS}) {
4199ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
42009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
42017cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                Cursor c = mDb.rawQuery("SELECT DISTINCT " +
42027cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            Calendar.SyncColumns._SYNC_ACCOUNT +
42037cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
42047cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            Calendar.SyncColumns._SYNC_ACCOUNT_TYPE +
42057cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
42069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
42079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (c.getString(0) != null && c.getString(1) != null) {
42089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
42099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
42109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
42119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
42129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
42139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
42149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
42159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
42169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
42179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
4218f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
4219f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
4220f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
42219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
4222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
42239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
42249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
42259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
42269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
42279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
42289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
42293ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
42303ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
4231dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
42329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
42339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4234636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
4235636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
4236636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
4237636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
4238636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
4239636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
4240636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
4241636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
4242636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
4243636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
4244636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
4245636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
4246636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
4247636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
4248636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
4249636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
4250636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
4251636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
4252636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
42539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
4254