CalendarProvider2.java revision 3443e3ebeaa39e8415b43e7cf3b218caee554e9b
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
20830f982e42eafaeb95b72fef9830167d39b025dcErikimport com.google.common.annotations.VisibleForTesting;
21830f982e42eafaeb95b72fef9830167d39b025dcErik
229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.AlarmManager;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.PendingIntent;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Debug;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
43f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglioimport android.pim.EventRecurrence;
449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet;
459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar;
479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Attendees;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.CalendarAlerts;
499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Calendars;
509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Events;
519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Instances;
529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Reminders;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
541edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
55192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Config;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
59ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
62ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
6568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglioimport java.util.List;
669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Set;
679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * is defined in {@link android.provider.Calendar}.
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String TAG = "CalendarProvider2";
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7763c1db9914a7b01181b71cc6c66d046ad7e794a9Erik    private static final String TIMEZONE_GMT = "GMT";
7863c1db9914a7b01181b71cc6c66d046ad7e794a9Erik
799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean PROFILE = false;
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
818f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
828f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff    private static final String INVALID_CALENDARALERTS_SELECTOR =
838f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            "_id IN (SELECT ca._id FROM CalendarAlerts AS ca"
848f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    + " LEFT OUTER JOIN Instances USING (event_id, begin, end)"
858f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    + " LEFT OUTER JOIN Reminders AS r ON"
868f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    + " (ca.event_id=r.event_id AND ca.minutes=r.minutes)"
878f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    + " WHERE Instances.begin ISNULL OR ca.alarmTime<?"
888f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    + "   OR (r.minutes ISNULL AND ca.minutes<>0))";
898f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
1027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3;
1037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
1067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
1077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
112646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
113646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
114646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
115646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
116646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
117646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
118646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
119646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
120646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
121646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
122646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
123646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
124646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
125646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
126646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
127646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
133ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
1369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private static final Uri SYNCSTATE_CONTENT_URI = Uri.parse("content://syncstate/state");
13883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    //
13983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false)
14083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true)
14183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // TODO: use a service to schedule alarms rather than private URI
14283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_PATH = "schedule_alarms";
14383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
14483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_URI =
14583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_PATH);
14683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_REMOVE_URI =
14783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
1489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // To determine if a recurrence exception originally overlapped the
1509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // window, we need to assume a maximum duration, since we only know
1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // the original start time.
1529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int MAX_ASSUMED_DURATION = 7*24*60*60*1000;
1539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1548ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
1558ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // Due to an issue in Calendar Server restricting the length of the name we had to strip it down
1568ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1578ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1588ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1598ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1608ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1613443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
1623443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            EventsRawTimesColumns.EVENT_ID + ", " +
1633443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            EventsRawTimesColumns.DTSTART_2445 + ", " +
1643443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            EventsRawTimesColumns.DTEND_2445 + ", " +
1653443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1663443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
1673443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            "EventsRawTimes" + ", " +
1683443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            "Events" +
1693443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
1703443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            EventsRawTimesColumns.EVENT_ID + " = " + "Events." + Events._ID;
1713443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio
1729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class TimeRange {
1739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public boolean allDay;
1769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesRange {
1799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public InstancesRange(long begin, long end) {
1839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.begin = begin;
1849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.end = end;
1859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
1869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesList
1899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends ArrayList<ContentValues> {
1909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class EventInstancesMap
1939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends HashMap<String, InstancesList> {
1941030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        public void add(String syncIdKey, ContentValues values) {
1951030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList instances = get(syncIdKey);
1969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (instances == null) {
1979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                instances = new InstancesList();
1981030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                put(syncIdKey, instances);
1999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instances.add(values);
2019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A thread that runs in the background and schedules the next
2059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // calendar event alarm.
2069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class AlarmScheduler extends Thread {
2079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean mRemoveAlarms;
2089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public AlarmScheduler(boolean removeAlarms) {
2109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mRemoveAlarms = removeAlarms;
2119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
213192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        @Override
2149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
2159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
2169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
2179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                runScheduleNextAlarm(mRemoveAlarms);
2189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (SQLException e) {
21952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
22052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.e(TAG, "runScheduleNextAlarm() failed", e);
22152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
2229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We search backward in time for event reminders that we may have missed
2289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and schedule them if the event has not yet expired.  The amount in
2299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the past to search backwards is controlled by this constant.  It
2309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should be at least a few minutes to allow for an event that was
2319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * recently created on the web to make its way to the phone.  Two hours
2329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * might seem like overkill, but it is useful in the case where the user
2339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * just crossed into a new timezone and might have just missed an alarm.
2349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2351edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff    private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS;
2369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Alarms older than this threshold will be deleted from the CalendarAlerts
2399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  This should be at least a day because if the timezone is
2409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * wrong and the user corrects it we might delete good alarms that
2419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * appear to be old because the device time was incorrectly in the future.
2429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This threshold must also be larger than SCHEDULE_ALARM_SLACK.  We add
2439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the SCHEDULE_ALARM_SLACK to ensure this.
2449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
2459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * To make it easier to find and debug problems with missed reminders,
2469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * set this to something greater than a day.
2479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long CLEAR_OLD_ALARM_THRESHOLD =
2491edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            7 * DateUtils.DAY_IN_MILLIS + SCHEDULE_ALARM_SLACK;
2509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A lock for synchronizing access to fields that are shared
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // with the AlarmScheduler thread.
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Object mAlarmLock = new Object();
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Allocate the string constant once here instead of on the heap
2649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String CALENDAR_ID_SELECTION = "calendar_id=?";
2659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sInstancesProjection =
2679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            new String[] { Instances.START_DAY, Instances.END_DAY,
2689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Instances.START_MINUTE, Instances.END_MINUTE, Instances.ALL_DAY };
2699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager mAlarmManager;
2779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarAppWidgetProvider mAppWidgetProvider = CalendarAppWidgetProvider.getInstance();
2799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
2829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
2859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
2869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
2879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
2889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
2899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
2919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
2969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
2989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
3009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
3029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
303ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
304ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Columns from the EventsRawTimes table
305ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
306ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    public interface EventsRawTimesColumns
307ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    {
308ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        /**
309ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * The corresponding event id
310ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * <P>Type: INTEGER (long)</P>
311ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         */
312ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        public static final String EVENT_ID = "event_id";
313ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
314ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        /**
315ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * The RFC2445 compliant time the event starts
316ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * <P>Type: TEXT</P>
317ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         */
318ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        public static final String DTSTART_2445 = "dtstart2445";
319ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
320ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        /**
321ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * The RFC2445 compliant time the event ends
322ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * <P>Type: TEXT</P>
323ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         */
324ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        public static final String DTEND_2445 = "dtend2445";
325ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
326ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        /**
327ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * The RFC2445 compliant original instance time of the recurring event for which this
328ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * event is an exception.
329ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * <P>Type: TEXT</P>
330ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         */
331ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        public static final String ORIGINAL_INSTANCE_TIME_2445 = "originalInstanceTime2445";
332ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
333ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        /**
334ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * The RFC2445 compliant last date this event repeats on, or NULL if it never ends
335ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         * <P>Type: TEXT</P>
336ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio         */
337ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        public static final String LAST_DATE_2445 = "lastDate2445";
338ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
339ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
3409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void verifyAccounts() {
3419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
3429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
3439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
3469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
3479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
3489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
3499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
3529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
3539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
3549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
3559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        verifyAccounts();
3579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
3599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
3609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
3629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
3639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
3649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Context c = getContext();
3659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
3679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
3689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
3699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
3709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        c.registerReceiver(mIntentReceiver, filter);
3719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mMetaData = new MetaData(mDbHelper);
373ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
374ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
3759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        updateTimezoneDependentFields();
3769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
3789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
3819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
3829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
38368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio     * has changed.
3849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
3869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
3879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
3889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
3919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
3929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
3939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
3949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                doUpdateTimezoneDependentFields();
3969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (SQLException e) {
39752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
39852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
39952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
4009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
4019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Clear at least the in-memory data (and if possible the
4029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // database fields) to force a re-computation of Instances.
4039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    mMetaData.clearInstanceRange();
4049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (SQLException e2) {
40552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
40652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.e(TAG, "clearInstanceRange() also failed: " + e2);
40752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
4089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
41468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio     * Check if we are in the same time zone
41568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio     */
41668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
41768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
41868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
41968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    }
42068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
42168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    /**
42268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio     * This method runs in a background thread.  If the timezone db or timezone has changed
4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
42568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
42668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String timezoneType = mCalendarCache.readTimezoneType();
42768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // Nothing to do if we have the "home" timezone type (timezone is sticky)
42868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
42968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            return;
43068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        }
43168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // We are here in "auto" mode, the timezone is coming from the device
432ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (! isSameTimezoneDatabaseVersion()) {
43368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
43468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
435ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
43668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (isLocalSameAsInstancesTimezone()) {
4379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Even if the timezone hasn't changed, check for missed alarms.
4389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This code executes when the CalendarProvider2 is created and
4399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // helps to catch missed alarms when the Calendar process is
4409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // killed (because of low-memory conditions) and then restarted.
4419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rescheduleMissedAlarms();
4429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
443ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
444ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
44568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
446ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
447ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (mDb == null) {
448ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
449ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Cannot update Events table from EventsRawTimes table");
450ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
451ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return;
452ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
453ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
454ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
4553443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
456ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
45768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
458ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
459ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
460ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
461ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
462ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
463ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
464ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
4653443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
4663443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
467ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
468ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
469ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
470ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
471ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
4723443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
47352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
47452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
47552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
47652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                + "at the same time in EventsRawTimes!");
47752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
47852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    continue;
47952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
480ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
4813443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
482ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
483ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
484ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
485ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
486ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
487ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
488ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
489ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
490ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
491ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
492ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
49352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
49452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.v( TAG, "Cannot parse null RFC2445 date");
49552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
496ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
497ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
498ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
499ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
500ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
501ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
50252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
50352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e( TAG, "Cannot parse RFC2445 date " + dt2445);
50452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
505ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
506ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
507ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
508ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
509ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
510ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
511ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
512ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
513ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
514ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        values.put("dtstart", get2445ToMillis(timezone, dtStart2445));
515ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        values.put("dtend", get2445ToMillis(timezone, dtEnd2445));
516ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
517dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff        int result = mDb.update("Events", values, "_id=?",
518dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
519ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
520ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
521ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
522ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
523ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
524ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
525ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
526ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
527ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
528ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
529ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
53052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
53152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
53252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
533ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
534ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
536ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
537ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
538ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
539ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
54068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
54168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (timezoneDatabaseVersion == null) {
542ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
543ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
544ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
545ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
546ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
54725e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
548ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
54968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
55068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (timezoneDatabaseVersion == null) {
551ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
552ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
55352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
55452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
55552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio        }
556ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
557ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
558ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
55968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private boolean isHomeTimezone() {
56068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
56168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
56268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    }
56368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
564ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
5659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
5669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
5679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
5689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
56968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
57068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        Time time = new Time(instancesTimezone);
5719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
5729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
5739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
5749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
5759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
5761f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
5779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
5789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
5791f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
5801f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
5811f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
5821f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
5831f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
5841f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
585d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    null /* selection */, null /* sort */,
586d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
58768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    true /* force Instances deletion and expansion */,
58868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    instancesTimezone,
58968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    isHomeTimezone());
5901f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
5911f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
5921f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
5931f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
5941f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
5959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rescheduleMissedAlarms();
5979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void rescheduleMissedAlarms() {
6009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
6019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager != null) {
6029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Context context = getContext();
6039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentResolver cr = context.getContentResolver();
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            CalendarAlerts.rescheduleMissedAlarms(cr, context, manager);
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
6099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Appends comma separated ids.
6109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param ids Should not be empty
6119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
6129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void appendIds(StringBuilder sb, HashSet<Long> ids) {
6139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (long id : ids) {
6149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            sb.append(id).append(',');
6159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sb.setLength(sb.length() - 1); // Yank the last comma
6189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
6219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void notifyChange() {
6229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
6239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
6249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        getContext().getContentResolver().notifyChange(Calendar.CONTENT_URI, null,
6259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                true /* syncToNetwork */);
6269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
6299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
6309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
631ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
632ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
6339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
6369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
6389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
6399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
64068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String instancesTimezone;
6419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
6439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
6449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
6459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
6469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
6479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
6491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
6509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
651595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
6529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
6539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
6541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
6559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
656636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
657636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
6589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
65919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
66019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
66119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
66219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
663595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
66419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
66519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
66619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
66719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
668636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
669636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
67019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
67119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
6729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
6739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
674595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
6759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
6769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
6779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
678636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
679636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
6809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
6819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
6829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
6839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
6849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
6859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
6869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
6879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
6889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
6899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
6909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
6919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
6929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
6939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
6949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
6959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
6969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
69768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
6989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return handleInstanceQuery(qb, begin, end, projection,
699d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                        selection, sortOrder, match == INSTANCES_BY_DAY,
70068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        false /* do not force Instances deletion and expansion */,
70168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        instancesTimezone, isHomeTimezone());
7026db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
7039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
7049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
7059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
7069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
7079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
7089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
7099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
7109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
7119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
7129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
7139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
7149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
7159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
7169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
71768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
71868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
71968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        instancesTimezone, isHomeTimezone());
7209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
7211ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
7229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
7231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.appendWhere("Events._id=Attendees.event_id");
7249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
7261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
7279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
728636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
729636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Attendees._id=?  AND Events._id=Attendees.event_id");
7309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
7329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Reminders");
7339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
7351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Reminders, Events");
7369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
737636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
738636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Reminders._id=? AND Events._id=Reminders.event_id");
7399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
741e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
7429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
743e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
744e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id");
7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
747e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
7489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
749e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
750e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id");
7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
754e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
756636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
757e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
758e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id AND CalendarAlerts._id=?");
7599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("ExtendedProperties");
7629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
7647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                qb.setTables("ExtendedProperties");
765636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
766636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("ExtendedProperties._id=?");
7679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
76868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            case PROVIDER_PROPERTIES:
76968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                qb.setTables("CalendarCache");
77068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
77168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                break;
7729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
7739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
7749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
7779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
7819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
783ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
784ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
785ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
786ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
787ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
788ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
789ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
790ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
791ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
7949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.setNotificationUri(getContext().getContentResolver(), Calendar.Events.CONTENT_URI);
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
812d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
81368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
81476f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
8169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
8179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
818d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio            long rangeEnd, String[] projection, String selection, String sort,
81968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            boolean searchByDay, boolean forceExpansion, String instancesTimezone,
82068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            boolean isHomeTimezone) {
8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setTables("Instances INNER JOIN Events ON (Instances.event_id=Events._id) " +
8239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                "INNER JOIN Calendars ON (Events.calendar_id = Calendars._id)");
8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
8269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
82868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            Time time = new Time(instancesTimezone);
8299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
8309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
8319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
8329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
8339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
83468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
83568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone
836d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio            );
8378335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff            qb.appendWhere("startDay<=? AND endDay>=?");
8389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
84068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
84168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone
842d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio            );
8438335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff            qb.appendWhere("begin<=? AND end>=?");
8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8458335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(rangeEnd),
8468335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
8478335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
8487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
8499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8516db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
85268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
85368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            boolean isHomeTimezone) {
8546db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setTables("Instances INNER JOIN Events ON (Instances.event_id=Events._id) " +
8556db535b458146a279bebd4a51d56c1bdfc204528Erik                "INNER JOIN Calendars ON (Events.calendar_id = Calendars._id)");
8566db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
85743556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
85843556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
85976f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio        Time time = new Time(instancesTimezone);
860192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
86143556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
86243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
863192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
86443556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
86568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
86668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
8678335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        qb.appendWhere("startDay<=? AND endDay>=?");
8688335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
8698335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
8708335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
8716db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  Acquires the database lock and calls {@link #acquireInstanceRangeLocked}.
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
881d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
88276f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
88376f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
8849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
885d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
88668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
88768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
89068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
89168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
8939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
8949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
8999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
9029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
9039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
90576f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
90676f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
90776f4f0375fa5892d3b2ab0c39d1fc367528b974cFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
90968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
91068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
9119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
91468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (instancesTimezone == null) {
91552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
91652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e(TAG, "Cannot run acquireInstanceRangeLocked() "
91752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        + "because instancesTimezone is null");
91852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
91968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            return;
92068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        }
92168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
9239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
9259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
94068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        boolean timezoneChanged;
94168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (isHomeTimezone) {
94268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
94368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
94468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        } else {
94568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
94668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
94763c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            // if we're in auto make sure we are using the device time zone
94863c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            if (timezoneChanged) {
94963c1db9914a7b01181b71cc6c66d046ad7e794a9Erik                instancesTimezone = localTimezone;
95063c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            }
95168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        }
95268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
95368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
954d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.execSQL("DELETE FROM Instances;");
95752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
9586db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
96168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
96268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
96368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
96568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
96663c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            // This may cause some double writes but guarantees the time zone in
96763c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            // the db and the time zone the instances are in is the same, which
96863c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            // future changes may affect.
96963c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
97063c1db9914a7b01181b71cc6c66d046ad7e794a9Erik
97163c1db9914a7b01181b71cc6c66d046ad7e794a9Erik            // If we're in auto check if we need to fix the previous tz value
97268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
97363c1db9914a7b01181b71cc6c66d046ad7e794a9Erik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
97463c1db9914a7b01181b71cc6c66d046ad7e794a9Erik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
97563c1db9914a7b01181b71cc6c66d046ad7e794a9Erik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
97663c1db9914a7b01181b71cc6c66d046ad7e794a9Erik                }
97768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            }
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
9839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
9849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
9859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
9869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
9879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
9889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
9899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
9909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
9919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
9929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
99352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
9949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
9959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
9969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
9979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
9989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
10029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
100368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
10049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
10089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
10099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
101068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
101468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // Update the bounds on the Instances table (timezone is the same here)
101568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
10169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EXPAND_COLUMNS = new String[] {
10199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._ID,
10209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
10219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.STATUS,
10229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTSTART,
10239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTEND,
10249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EVENT_TIMEZONE,
10259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
10269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
10279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXRULE,
10289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXDATE,
10299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DURATION,
10309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ALL_DAY,
10319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
10321030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            Events.ORIGINAL_INSTANCE_TIME,
10331dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio            Events.CALENDAR_ID,
10341dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio            Events.DELETED
10359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
10369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make instances for the given range.
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void expandInstanceRangeLocked(long begin, long end, String localTimezone) {
10419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
10439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.startMethodTracing("expandInstanceRangeLocked");
10449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
10479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Expanding events between " + begin + " and " + end);
10489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getEntries(begin, end);
10519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
10529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            performInstanceExpansion(begin, end, localTimezone, entries);
10539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
10549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
10559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
10569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
10579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
10599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.stopMethodTracing();
10609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
10649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Get all entries affecting the given window.
10659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
10669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
10679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return Cursor for the entries; caller must close it.
10689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getEntries(long begin, long end) {
10709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
10711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
10729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
10739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String beginString = String.valueOf(begin);
10759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String endString = String.valueOf(end);
10769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // grab recurrence exceptions that fall outside our expansion window but modify
10789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences that do fall within our window.  we won't insert these into the output
10799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // set of instances, but instead will just add them to our cancellations list, so we
10809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // can cancel the correct recurrence expansion instances.
10819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we don't have originalInstanceDuration or end time.  for now, assume the original
10829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // instance lasts no longer than 1 week.
10832d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio        // also filter with syncable state (we dont want the entries from a non syncable account)
10849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: compute the originalInstanceEndTime or get this from the server.
10852d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio        qb.appendWhere("((dtstart <= ? AND (lastDate IS NULL OR lastDate >= ?)) OR " +
10868335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                "(originalInstanceTime IS NOT NULL AND originalInstanceTime <= ? AND " +
10872d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio                "originalInstanceTime >= ?)) AND (sync_events != 0)");
10888335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {endString, beginString, endString,
10898335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(begin - MAX_ASSUMED_DURATION)};
1090e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        Cursor c = qb.query(mDb, EXPAND_COLUMNS, null /* selection */,
10918335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                selectionArgs, null /* groupBy */,
10927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
1093e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1094e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            Log.v(TAG, "Instance expansion:  got " + c.getCount() + " entries");
1095e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
1096e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        return c;
10979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
11001030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * Generates a unique key from the syncId and calendarId.
11011030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * The purpose of this is to prevent collisions if two different calendars use the
11021030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * same sync id.  This can happen if a Google calendar is accessed by two different accounts,
11031030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * or with Exchange, where ids are not unique between calendars.
11041030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param syncId Id for the event
11051030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param calendarId Id for the calendar
11061030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @return key
11071030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     */
11081030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    private String getSyncIdKey(String syncId, long calendarId) {
11091030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        return calendarId + ":" + syncId;
11101030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    }
11111030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
11121030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    /**
11139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Perform instance expansion on the given entries.
11149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
11159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
11169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param localTimezone
11179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param entries The entries to process.
11189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
11199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void performInstanceExpansion(long begin, long end, String localTimezone,
11209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                          Cursor entries) {
11219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        RecurrenceProcessor rp = new RecurrenceProcessor();
11229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11231030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // Key into the instance values to hold the original event concatenated with calendar id.
11241030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        final String ORIGINAL_EVENT_AND_CALENDAR = "ORIGINAL_EVENT_AND_CALENDAR";
11251030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
11269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int statusColumn = entries.getColumnIndex(Events.STATUS);
11279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
11289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtendColumn = entries.getColumnIndex(Events.DTEND);
11299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
11309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int durationColumn = entries.getColumnIndex(Events.DURATION);
11319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rruleColumn = entries.getColumnIndex(Events.RRULE);
11329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rdateColumn = entries.getColumnIndex(Events.RDATE);
11339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exruleColumn = entries.getColumnIndex(Events.EXRULE);
11349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exdateColumn = entries.getColumnIndex(Events.EXDATE);
11359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
11369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int idColumn = entries.getColumnIndex(Events._ID);
11379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
11389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
11399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
11401030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        int calendarIdColumn = entries.getColumnIndex(Events.CALENDAR_ID);
11411dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio        int deletedColumn = entries.getColumnIndex(Events.DELETED);
11429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues initialValues;
11449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        EventInstancesMap instancesMap = new EventInstancesMap();
11459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Duration duration = new Duration();
11479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time eventTime = new Time();
11489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: entries contains all events that affect the current
11509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // window.  It consists of:
11519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  These will be
11529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed.
11539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Recurrences that included the window.  These will be displayed
11549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    if not canceled.
11559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Recurrence exceptions that fall in the window.  These will be
11569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed if not cancellations.
11579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
11589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    window (subject to 1 week assumption above), but are outside
11599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    the window.  These will not be displayed.  Cases c and d are
11609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    distingushed by the start / end time.
11619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        while (entries.moveToNext()) {
11639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
11649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                initialValues = null;
11659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                boolean allDay = entries.getInt(allDayColumn) != 0;
11679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String eventTimezone = entries.getString(eventTimezoneColumn);
11699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (allDay || TextUtils.isEmpty(eventTimezone)) {
11709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // in the events table, allDay events start at midnight.
11719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this forces them to stay at midnight for all day events
11729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: check that this actually does the right thing.
11739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTimezone = Time.TIMEZONE_UTC;
11749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
11759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long dtstartMillis = entries.getLong(dtstartColumn);
11779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Long eventId = Long.valueOf(entries.getLong(idColumn));
11789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String durationStr = entries.getString(durationColumn);
11809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (durationStr != null) {
11819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    try {
11829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.parse(durationStr);
11839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
11849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    catch (DateException e) {
118552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        if (Log.isLoggable(TAG, Log.WARN)) {
118652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            Log.w(TAG, "error parsing duration for event "
118752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                    + eventId + "'" + durationStr + "'", e);
118852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        }
11899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.sign = 1;
11909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.weeks = 0;
11919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.days = 0;
11929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.hours = 0;
11939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.minutes = 0;
11949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.seconds = 0;
11959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        durationStr = "+P0S";
11969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
11979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
11989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String syncId = entries.getString(syncIdColumn);
12009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String originalEvent = entries.getString(originalEventColumn);
12019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalInstanceTimeMillis = -1;
12039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!entries.isNull(originalInstanceTimeColumn)) {
12049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn);
12059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int status = entries.getInt(statusColumn);
12071dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                boolean deleted = (entries.getInt(deletedColumn) != 0);
12089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rruleStr = entries.getString(rruleColumn);
12109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rdateStr = entries.getString(rdateColumn);
12119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exruleStr = entries.getString(exruleColumn);
12129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exdateStr = entries.getString(exdateColumn);
12131030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                long calendarId = entries.getLong(calendarIdColumn);
12141030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String syncIdKey = getSyncIdKey(syncId, calendarId); // key into instancesMap
12159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1216f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                RecurrenceSet recur = null;
1217f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                try {
1218f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr);
1219f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                } catch (EventRecurrence.InvalidFormatException e) {
122052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
122152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.w(TAG, "Could not parse RRULE recurrence string: " + rruleStr, e);
122252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
1223f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    continue;
1224f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                }
12259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1226f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                if (null != recur && recur.hasRecurrence()) {
12279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is repeating
12289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (status == Events.STATUS_CANCELED) {
12309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen!
123152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
123252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            Log.e(TAG, "Found canceled recurring event in "
123352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                    + "Events table.  Ignoring.");
123452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        }
12359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        continue;
12369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
12379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // need to parse the event into a local calendar.
12399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.timezone = eventTimezone;
12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.set(dtstartMillis);
12419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.allDay = allDay;
12429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
12449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen.
124552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
124652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            Log.e(TAG, "Repeating event has no duration -- "
124752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                    + "should not happen.");
124852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        }
12499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (allDay) {
12509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // set to one day.
12519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
12529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 1;
12549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
12559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
12569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.seconds = 0;
12579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            durationStr = "+P1D";
12589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
12599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // compute the duration from dtend, if we can.
12609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // otherwise, use 0s.
12619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
12629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
12639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 0;
12649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
12659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
12669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            if (!entries.isNull(dtendColumn)) {
12679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                long dtendMillis = entries.getLong(dtendColumn);
12689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000);
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P" + duration.seconds + "S";
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            } else {
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = 0;
12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P0S";
12739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            }
12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long[] dates;
12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    dates = rp.expand(eventTime, recur, begin, end);
12799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Initialize the "eventTime" timezone outside the loop.
12819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This is used in computeTimezoneDependentFields().
12829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
12839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
12849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
12859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
12869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
12879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long durationMillis = duration.getMillis();
12899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    for (long date : dates) {
12909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues = new ContentValues();
12919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.EVENT_ID, eventId);
12929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.BEGIN, date);
12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        long dtendMillis = date + durationMillis;
12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.END, dtendMillis);
12969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        computeTimezoneDependentFields(date, dtendMillis,
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                eventTime, initialValues);
12991030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        instancesMap.add(syncIdKey, initialValues);
13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else {
13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is not repeating
13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues = new ContentValues();
13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // if this event has an "original" field, then record
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // that we need to cancel the original event (we can't
13079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // do that here because the order of this loop isn't
13089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // defined)
13099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (originalEvent != null && originalInstanceTimeMillis != -1) {
13101030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // The ORIGINAL_EVENT_AND_CALENDAR holds the
13111030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // calendar id concatenated with the ORIGINAL_EVENT to form
13121030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // a unique key, matching the keys for instancesMap.
13131030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        initialValues.put(ORIGINAL_EVENT_AND_CALENDAR,
13141030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                                getSyncIdKey(originalEvent, calendarId));
13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.ORIGINAL_INSTANCE_TIME,
13169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                originalInstanceTimeMillis);
13179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.STATUS, status);
13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
13199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long dtendMillis = dtstartMillis;
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!entries.isNull(dtendColumn)) {
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            dtendMillis = entries.getLong(dtendColumn);
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        dtendMillis = duration.addTo(dtstartMillis);
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this non-recurring event might be a recurrence exception that doesn't
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // actually fall within our expansion window, but instead was selected
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so we can correctly cancel expanded recurrence instances below.  do not
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // add events to the instances map if they don't actually fall within our
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // expansion window.
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if ((dtendMillis < begin) || (dtstartMillis > end)) {
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (originalEvent != null && originalInstanceTimeMillis != -1) {
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            initialValues.put(Events.STATUS, Events.STATUS_CANCELED);
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
133852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            if (Log.isLoggable(TAG, Log.WARN)) {
133952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                Log.w(TAG, "Unexpected event outside window: " + syncId);
134052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            }
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            continue;
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.EVENT_ID, eventId);
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13471dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    initialValues.put(Instances.BEGIN, dtstartMillis);
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.END, dtendMillis);
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13501dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    // we temporarily store the DELETED status (will be cleaned later)
13511dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    initialValues.put(Events.DELETED, deleted);
13521dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    computeTimezoneDependentFields(dtstartMillis, dtendMillis,
13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            eventTime, initialValues);
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13611030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                    instancesMap.add(syncIdKey, initialValues);
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (DateException e) {
136452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
136552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
136652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (TimeFormatException e) {
136852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.VERBOSE)) {
136952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
137052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
13719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains all instances that affect the
13751030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // window, indexed by original sync id concatenated with calendar id.
13761030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // It consists of:
13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  They have:
13789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window.  They may
13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be subject to exceptions.  They have:
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They have:
13831030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS (since they can
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be a modification or cancellation), EVENT_ID, BEGIN, END
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  They have:
13871030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS =
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED, EVENT_ID, BEGIN, END
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // First, delete the original instances corresponding to recurrence
13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // exceptions.  We do this by iterating over the list and for each
13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrence exception, we search the list for an instance with a
13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // matching "original instance time".  If we find such an instance,
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we remove it from the list.  If we don't find such an instance
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then we cancel the recurrence exception.
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Set<String> keys = instancesMap.keySet();
13971030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
13981030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
13999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
14009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // If this instance is not a recurrence exception, then
14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // skip it.
14031030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                if (!values.containsKey(ORIGINAL_EVENT_AND_CALENDAR)) {
14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14071030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String originalEventPlusCalendar = values.getAsString(ORIGINAL_EVENT_AND_CALENDAR);
14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
14091030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                InstancesList originalList = instancesMap.get(originalEventPlusCalendar);
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (originalList == null) {
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // The original recurrence is not present, so don't try canceling it.
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Search the original event for a matching original
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance time.  If there is a matching one, then remove
14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the original one.  We do this both for exceptions that
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // change the original instance as well as for exceptions
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // that delete the original instance.
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                for (int num = originalList.size() - 1; num >= 0; num--) {
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    ContentValues originalValues = originalList.get(num);
14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long beginTime = originalValues.getAsLong(Instances.BEGIN);
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (beginTime == originalTime) {
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // We found the original instance, so remove it.
14259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        originalList.remove(num);
14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
14279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains filtered instances.
14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // It consists of:
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window and have not
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   been subject to exceptions.
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They will have
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED if they are cancellations.
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  These are STATUS_CANCELED.
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do the inserts.  Since the db lock is held when this method is executed,
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // this will be done in a transaction.
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // while the calendar app is trying to query the db (expanding instances)), we will
14459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // not be "polite" and yield the lock until we're done.  This will favor local query
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // operations over sync/write operations.
14471030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
14481030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
14499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
14509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14511dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // If this instance was cancelled or deleted then don't create a new
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance.
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer status = values.getAsInteger(Events.STATUS);
14541dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                boolean deleted = values.containsKey(Events.DELETED) ?
14551dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                        values.getAsBoolean(Events.DELETED) : false;
14561dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                if ((status != null && status == Events.STATUS_CANCELED) || deleted) {
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14601dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // We remove this useless key (not valid in the context of Instances table)
14611dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                values.remove(Events.DELETED);
14621dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
14639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Remove these fields before inserting a new instance
14641030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                values.remove(ORIGINAL_EVENT_AND_CALENDAR);
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.STATUS);
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1468c874ed5c6cc0fcc6ac06ae7d20db0eab7d749608Ken Shirriff                mDbHelper.instancesReplace(values);
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Computes the timezone-dependent fields of an instance of an event and
14759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * updates the "values" map to contain those fields.
14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin the start time of the instance (in UTC milliseconds)
14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end the end time of the instance (in UTC milliseconds)
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param local a Time object with the timezone set to the local timezone
14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values a map that will contain the timezone-dependent fields
14819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void computeTimezoneDependentFields(long begin, long end,
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local, ContentValues values) {
14849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(begin);
14859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startDay = Time.getJulianDay(begin, local.gmtoff);
14869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startMinute = local.hour * 60 + local.minute;
14879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(end);
14899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endDay = Time.getJulianDay(end, local.gmtoff);
14909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endMinute = local.hour * 60 + local.minute;
14919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Special case for midnight, which has endMinute == 0.  Change
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that to +24 hours on the previous day to make everything simpler.
14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Exception: if start and end minute are both 0 on the same day,
14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then leave endMinute alone.
14969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (endMinute == 0 && endDay > startDay) {
14979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endMinute = 24 * 60;
14989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endDay -= 1;
14999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_DAY, startDay);
15029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_DAY, endDay);
15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_MINUTE, startMinute);
15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_MINUTE, endMinute);
15059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
15089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
15099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
15109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
15119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
15129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
15139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
15149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
15159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
15179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
15189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
15199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
15209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
15219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
15229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
15239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
15249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
15259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
15269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
15276db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
15289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
152948587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
153048587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
153168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            case PROVIDER_PROPERTIES:
153268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                return "vnd.android.cursor.dir/property";
15339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
15349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
15359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static boolean isRecurrenceEvent(ContentValues values) {
15399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return (!TextUtils.isEmpty(values.getAsString(Events.RRULE))||
15409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.RDATE))||
15419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT)));
15429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1544646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1545646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1546646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1547646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1548646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * corrects the fields DTSTART, DTEND, and DURATION if necessary. Also checks to ensure that
1549646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * either both DTSTART and DTEND or DTSTART and DURATION are set for each event.
1550646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1551646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @param updatedValues The values to check and correct
1552646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1553646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1554646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private boolean fixAllDayTime(Uri uri, ContentValues updatedValues) {
1555646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1556646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        if (updatedValues.containsKey(Events.ALL_DAY)
1557646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                && updatedValues.getAsInteger(Events.ALL_DAY).intValue() == 1) {
1558646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtstart = updatedValues.getAsLong(Events.DTSTART);
1559646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtend = updatedValues.getAsLong(Events.DTEND);
1560646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String duration = updatedValues.getAsString(Events.DURATION);
1561646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Time time = new Time();
1562646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Cursor currentTimesCursor = null;
1563646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String tempValue;
1564646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If a complete set of time fields doesn't exist query the db for them. A complete set
1565646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // is dtstart and dtend for non-recurring events or dtstart and duration for recurring
1566646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // events.
1567646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if(dtstart == null || (dtend == null && duration == null)) {
1568646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we have an id to search for, if not this is probably a new event
1569646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 2) {
1570646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    currentTimesCursor = query(uri,
1571646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            ALLDAY_TIME_PROJECTION,
1572646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selection */,
1573646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selectionArgs */,
1574646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* sort */);
1575646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    if (currentTimesCursor != null) {
1576646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (!currentTimesCursor.moveToFirst() ||
1577646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                                currentTimesCursor.getCount() != 1) {
1578646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // Either this is a new event or the query is too general to get data
1579646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // from the db. In either case don't try to use the query and catch
1580646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // errors when trying to update the time fields.
1581646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor.close();
1582646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor = null;
1583646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
1584646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1585646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1586646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1587646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1588646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // Ensure dtstart exists for this event (always required) and set so h,m,s are 0 if
1589646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // necessary.
1590646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // TODO Move this somewhere to check all events, not just allDay events.
1591646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtstart == null) {
1592646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (currentTimesCursor != null) {
1593646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // getLong returns 0 for empty fields, we'd like to know if a field is empty
1594646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // so getString is used instead.
1595646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    tempValue = currentTimesCursor.getString(ALLDAY_DTSTART_INDEX);
1596646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    try {
1597646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        dtstart = Long.valueOf(tempValue);
1598646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    } catch (NumberFormatException e) {
1599646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        currentTimesCursor.close();
1600646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        throw new IllegalArgumentException("Event has no DTSTART field, the db " +
1601646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            "may be damaged. Set DTSTART for this event to fix.");
1602646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1603646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
1604646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    throw new IllegalArgumentException("DTSTART cannot be empty for new events.");
1605646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1606646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1607646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1608646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.set(dtstart.longValue());
1609646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1610646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1611646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1612646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1613646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                updatedValues.put(Events.DTSTART, time.toMillis(true));
1614646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1615646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1616646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1617646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If dtend exists for this event make sure it's h,m,s are 0.
1618646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend == null && currentTimesCursor != null) {
1619646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // getLong returns 0 for empty fields. We'd like to know if a field is empty
1620646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // so getString is used instead.
1621646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                tempValue = currentTimesCursor.getString(ALLDAY_DTEND_INDEX);
1622646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                try {
1623646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = Long.valueOf(tempValue);
1624646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } catch (NumberFormatException e) {
1625646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = null;
1626646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1627646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1628646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend != null) {
1629646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.clear(Time.TIMEZONE_UTC);
1630646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.set(dtend.longValue());
1631646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1632646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.hour = 0;
1633646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.minute = 0;
1634646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.second = 0;
1635646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = time.toMillis(true);
1636646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DTEND, dtend);
1637646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1638646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1639646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1640646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1641646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (currentTimesCursor != null) {
1642646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (duration == null) {
1643646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = currentTimesCursor.getString(ALLDAY_DURATION_INDEX);
1644646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1645646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                currentTimesCursor.close();
1646646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1647646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1648646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration != null) {
1649646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                int len = duration.length();
1650646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1651646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 * in the seconds format, and if so converts it to days.
1652646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 */
1653646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (len == 0) {
1654646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = null;
1655646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else if (duration.charAt(0) == 'P' &&
1656646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        duration.charAt(len - 1) == 'S') {
1657646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int seconds = Integer.parseInt(duration.substring(1, len - 1));
1658646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1659646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = "P" + days + "D";
1660646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DURATION, duration);
1661646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1662646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else if (duration.charAt(0) != 'P' ||
1663646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        duration.charAt(len - 1) != 'D') {
1664646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    throw new IllegalArgumentException("duration is not formatted correctly. " +
1665646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            "Should be 'P<seconds>S' or 'P<days>D'.");
1666646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1667646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1668646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1669646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration == null && dtend == null) {
1670646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
1671646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        "an event.");
1672646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1673646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1674646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1675646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1676646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
16779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
16789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected Uri insertInTransaction(Uri uri, ContentValues values) {
1679ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
16809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
16819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
16849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
16859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
16879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
16889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
16909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff              case SYNCSTATE:
16919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
16929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
16939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
16947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
16957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
16967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
16979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
16989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
16999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
1701e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
1702e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                validateEventData(updatedValues);
1703e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
1704e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
17059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
17069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
17079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
17089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
17109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
17119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
17129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
17139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
17149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
17159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
17169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
17179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
17189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
17199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
17209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
1721646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(uri, updatedValues)) {
172252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
172352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
172452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
172552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
1726646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
17279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
17289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
17299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
17309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, true /* new event */, mDb);
17319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
17339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
17349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
17359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
17369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
17379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
17389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
17399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
17409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
17418ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // if the Event Timezone is defined, store it as the original one in the
17428ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // ExtendedProperties table
17438ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    if (values.containsKey(Events.EVENT_TIMEZONE) && !callerIsSyncAdapter) {
17448ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        String originalTimezone = values.getAsString(Events.EVENT_TIMEZONE);
17458ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
17468ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        ContentValues expropsValues = new ContentValues();
17478ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.EVENT_ID, id);
17488ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.NAME,
17498ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                EXT_PROP_ORIGINAL_TIMEZONE);
17508ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.VALUE, originalTimezone);
17518ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
17528ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        // Insert the extended property
17538ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        long exPropId = mDbHelper.extendedPropertiesInsert(expropsValues);
17548ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        if (exPropId == -1) {
17558ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
17568ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                Log.e(TAG, "Cannot add the original Timezone in the "
17578ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                        + "ExtendedProperties table for Event: " + id);
17588ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
17598ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        } else {
17608ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            // Update the Event for saying it has some extended properties
17618ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            ContentValues eventValues = new ContentValues();
17628ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            eventValues.put(Events.HAS_EXTENDED_PROPERTIES, "1");
17638ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            int result = mDb.update("Events", eventValues, "_id=?",
17648ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    new String[] {String.valueOf(id)});
17658ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (result <= 0) {
17668ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                if (Log.isLoggable(TAG, Log.ERROR)) {
17678ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    Log.e(TAG, "Cannot update hasExtendedProperties column"
17688ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                            + " for Event: " + id);
17698ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                }
17708ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
17718ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        }
17728ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    }
17739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    triggerAppWidgetUpdate(id);
17749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
17769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
17779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
17789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
17799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountName = values.getAsString(Calendars._SYNC_ACCOUNT);
17809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
17819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Calendars._SYNC_ACCOUNT_TYPE);
17829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
17839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String calendarUrl = values.getAsString(Calendars.URL);
17849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    mDbHelper.scheduleSync(account, false /* two-way sync */, calendarUrl);
17859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
17879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
17889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
17899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
17909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
17919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
17929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.attendeesInsert(values);
17947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
17957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Attendees.EVENT_ID));
17967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
17979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
17999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
18009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
18019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
18029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
18039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
18049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
18059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.remindersInsert(values);
18077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
18087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Reminders.EVENT_ID));
18097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
18129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
18139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
18149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
18169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
18179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
18189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
18199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
18209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
18219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
18232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
18242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
18259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
18269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
18279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Calendar.ExtendedProperties.EVENT_ID)) {
18289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
18299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
18309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.extendedPropertiesInsert(values);
18327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
18337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Calendar.ExtendedProperties.EVENT_ID));
18347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
18369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
18379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
18389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
18399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
18409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
18419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
18429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
18436db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
184468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            case PROVIDER_PROPERTIES:
18457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
18469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
18479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
18489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
18519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
18529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
18559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1857e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
1858e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Do some validation on event data before inserting.
1859e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * In particular make sure dtend, duration, etc make sense for
1860e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * the type of event (regular, recurrence, exception).  Remove
1861e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * any unexpected fields.
1862e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
1863e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues to insert
1864e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
1865e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private void validateEventData(ContentValues values) {
1866e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
1867e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
1868e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
1869e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
1870e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT));
1871e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
1872e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
1873e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
1874e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
1875e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
1876e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
1877e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is the recurrence rule
1878e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
1879e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
1880e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
1881e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
1882e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
1883e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence: " + values);
1884e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
1885e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
1886e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_EVENT);
1887e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
1888e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
1889e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
1890e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
1891e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
1892e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
1893e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
1894e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
1895e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
1896e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
1897e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
1898e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
1899e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
1900e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence exception: " + values);
1901e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
1902e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
1903e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
1904e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
1905e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
1906e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
1907e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
1908e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
1909e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
1910e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
1911e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
1912e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
1913e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
1914e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
1915e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for event: " + values);
1916e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
1917e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
1918e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
1919e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
1920e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
1921e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
19227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private void setEventDirty(int eventId) {
1923636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=?", new Integer[] {eventId});
19247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
19257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
19269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
19279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
19289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
19299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
19309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
19319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
1932f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
193352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
193452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
193552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
1936f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
1937f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
19389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
19399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
19409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
19419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
19429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
19439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
19449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
19459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
19469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
19479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
194852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
194952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
195052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
19519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
19529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
19539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
19549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
19559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
19569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
19579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
19589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
19599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
19609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
19619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
19639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
19649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
19659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
19669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
19679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
19689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
19699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
19709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
19719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
19729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
19739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
19749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
19759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
19769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
19779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
19789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
19799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
19809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
19819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
19839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
19849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
19859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
19869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
19889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
19899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
19909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
19919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
19929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
19939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
19949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
19959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
19969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
19979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
19989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
20009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
20019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
20029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
20039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
20049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
20059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
20069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
20079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
20089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
20099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
201052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
201152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
201252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
20139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
20149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
20169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
20179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
20189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
20199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
20239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
20249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
20259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
20269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
20279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
20289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
20299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
20309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
20319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
203252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
203352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
203452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
20359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
20369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
20389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
20399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
20409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
20419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
20499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
20639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
20649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
20659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
20669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
20679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
20759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2076636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        db.update("Events", values, "_id=?", new String[] {String.valueOf(eventId)});
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the instances table when an event is added or updated.
20819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
20829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
20839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param newEvent true if the event is new.
20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
20859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateInstancesLocked(ContentValues values,
20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean newEvent,
20899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
20909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If there are no expanded Instances, then return.
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (fields.maxInstance == 0) {
20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
20959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
20989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis == null) {
20999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (newEvent) {
21009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // must be present for a new event.
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART missing.");
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
210352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
210452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.v(TAG, "Missing DTSTART.  No need to update instance.");
210552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
21109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
21119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!newEvent) {
21139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Want to do this for regular event, recurrence, or exception.
21149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // For recurrence or exception, more deletion may happen below if we
21159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // do an instance expansion.  This deletion will suffice if the exception
21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // is moved outside the window, for instance.
2117636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (isRecurrenceEvent(values))  {
21219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // The recurrence or exception needs to be (re-)expanded if:
21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // a) Exception or recurrence that falls inside window
21239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean insideWindow = dtstartMillis <= fields.maxInstance &&
21249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    (lastDateMillis == null || lastDateMillis >= fields.minInstance);
21259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // b) Exception that affects instance inside window
21269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // These conditions match the query in getEntries
21279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            //  See getEntries comment for explanation of subtracting 1 week.
21289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean affectsWindow = originalInstanceTime != null &&
21299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime <= fields.maxInstance &&
21309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (insideWindow || affectsWindow) {
21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateRecurrenceInstancesLocked(values, rowId, db);
21339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: an exception creation or update could be optimized by
21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // updating just the affected instances, instead of regenerating
21369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the recurrence.
21379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
21389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis == null) {
21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            dtendMillis = dtstartMillis;
21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // if the event is in the expanded range, insert
21469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // into the instances table.
21479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: deal with durations.  currently, durations are only used in
21489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences.
21499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
21519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentValues instanceValues = new ContentValues();
21529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.EVENT_ID, rowId);
21539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.BEGIN, dtstartMillis);
21549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.END, dtendMillis);
21559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean allDay = false;
21579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
21589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
21599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                allDay = allDayInteger != 0;
21609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Update the timezone-dependent fields.
21639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local = new Time();
21649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDay) {
21659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = Time.TIMEZONE_UTC;
21669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
21679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = fields.timezone;
21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
21719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.instancesInsert(instanceValues);
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
21749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
21769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Determines the recurrence entries associated with a particular recurrence.
21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This set is the base recurrence and any exception.
21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Normally the entries are indicated by the sync id of the base recurrence
21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * (which is the originalEvent in the exceptions).
21819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * However, a complication is that a recurrence may not yet have a sync id.
21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * In that case, the recurrence is specified by the rowId.
21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
21849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param recurrenceSyncId The sync id of the base recurrence, or null.
21859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The row id of the base recurrence.
21869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return the relevant entries.
21879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
21889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) {
21899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
21909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
21929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
2193636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        String selectionArgs[];
21949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
2195636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_id =?";
21969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2197636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {String.valueOf(rowId)};
21989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
2199636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_sync_id = ? OR originalEvent = ?";
22009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2201636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId};
22029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
22049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Retrieving events to expand: " + qb.toString());
22059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2207636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs,
22087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* groupBy */, null /* having */, null /* sortOrder */);
22099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Do incremental Instances update of a recurrence or recurrence exception.
22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method does performInstanceExpansion on just the modified recurrence,
22159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid the overhead of recomputing the entire instance table.
22169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
22189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
22199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
22209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateRecurrenceInstancesLocked(ContentValues values,
22229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
22239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
22249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
222568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
22269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
222768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String recurrenceSyncId;
22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalEvent != null) {
22299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = originalEvent;
22309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
22319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the recurrence's sync id from the database
22329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = DatabaseUtils.stringForQuery(db, "SELECT _sync_id FROM Events"
2233636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    + " WHERE _id=?", new String[] {String.valueOf(rowId)});
22349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrenceSyncId is the _sync_id of the underlying recurrence
22369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the recurrence hasn't gone to the server, it will be null.
22379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Need to clear out old instances
22399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
22409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating updating a recurrence that hasn't gone to the server.
22419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Need to delete based on row id
22429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
22439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
22449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
22459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._id =?)";
22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{"" + rowId});
22479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
22489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating or modifying a recurrence or exception.
22499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Delete instances for recurrence (_sync_id = recurrenceSyncId)
22509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // and all exceptions (originalEvent = recurrenceSyncId)
22519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._sync_id =?"
22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " OR Events.originalEvent =?)";
22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{recurrenceSyncId, recurrenceSyncId});
22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do instance expansion
22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId);
22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
226268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            performInstanceExpansion(fields.minInstance, fields.maxInstance, instancesTimezone,
22639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                     entries);
22649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
22659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
22669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
22679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
22749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
22759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
22769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
22779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
22789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
22989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2305f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2306f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2307f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2308f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
230952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
231052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
231152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            values.get(Calendar.Events.RRULE), e);
231252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
2313f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2314f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
23159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2316f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
23429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2346e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2347e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
2348e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues (in/out)
2349e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2350e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2351e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
23529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
23539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
23599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
23609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
236152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
236252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
236352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
23709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rawValues.put("event_id", eventId);
23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
23749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
23839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtstart2445", time.format2445());
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
23979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtend2445", time.format2445());
23989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
24019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
24029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
24039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
24049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
24059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
24069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
24079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
24089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
24109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("originalInstanceTime2445", time.format2445());
24119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("lastDate2445", time.format2445());
24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
2425ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
2436dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
24389323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2439dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2440dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2441dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2442dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
24439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
24467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
24471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                selection = appendAccountToSelection(uri, selection);
24487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
24491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
24501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                Cursor cursor = mDb.query("Events", ID_ONLY_PROJECTION,
24511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
24527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
24541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
24551ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
245610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
245810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                    scheduleNextAlarm(false /* do not remove alarms */);
245910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                    triggerAppWidgetUpdate(-1 /* changedEventId */);
24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
24619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
24639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
24659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
24671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
24681ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
24691ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
24701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
24711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
24721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
24731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
247410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
24751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
24787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
24797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Attendees", selection, selectionArgs);
24807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
24817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Attendees", uri, selection, selectionArgs);
24827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
24862fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
24872fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
24882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
24897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
24907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2491636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Attendees", "_id=?", new String[] {String.valueOf(id)});
24927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
24932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Attendees", uri, null /* selection */,
24942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
24957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
24997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
25007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Reminders", selection, selectionArgs);
25017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
25027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Reminders", uri, selection, selectionArgs);
25037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25072fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
25082fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
25092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
25117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2512636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Reminders", "_id=?", new String[] {String.valueOf(id)});
25137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
25142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Reminders", uri, null /* selection */,
25152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
25162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
25182fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
25192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
25202fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
25212fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return mDb.delete("ExtendedProperties", selection, selectionArgs);
25222fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
25232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, selection, selectionArgs);
25242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25252fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
25262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
25272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
25282fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
25292fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
25302fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25312fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
25322fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
2533636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("ExtendedProperties", "_id=?",
2534636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
25352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
25362fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, null /* selection */,
25372fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
25387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
25399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
25419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
25437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("CalendarAlerts", selection, selectionArgs);
25447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
25457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("CalendarAlerts", uri, selection, selectionArgs);
25467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
25479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
25499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25502fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
25512fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
25522fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25532fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
25542fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2556636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.delete("CalendarAlerts", "_id=?", new String[] {String.valueOf(id)});
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
25597e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
25619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                StringBuilder selectionSb = new StringBuilder("_id=");
25629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
25639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
25649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
25659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
25669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
25679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
25699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
25709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
2571595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
25727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return deleteMatchingCalendars(selection); // TODO: handle in sync adapter
25739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
25749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
25756db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
257668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            case PROVIDER_PROPERTIES:
25779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
25789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
25799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
25809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
258310b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
25841ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
2585192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
25861ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
25871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
25881ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        Cursor cursor = mDb.query("Events", EVENTS_PROJECTION,
2589192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                "_id=?", selectionArgs,
2590636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
25911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
25921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
25931ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
25941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
25951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
259648f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
259748f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (!emptySyncId) {
25981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
25991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // TODO: we may also want to delete exception
26001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // events for this event (in case this was a
26011ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // recurring event).  We can do that with the
26021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // following code:
26031ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // mDb.delete("Events", "originalEvent=?", new String[] {syncId});
26041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
26051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
26061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
26071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
26081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
26091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
26101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
26111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX);
26121ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)
26131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        || !TextUtils.isEmpty(origEvent)) {
26141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
26151ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
26161ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
261748f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
261848f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
261948f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
2620192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                    mDb.delete("Events", "_id=?", selectionArgs);
2621192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                    mDb.delete("Attendees", "event_id=?", selectionArgs);
26221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
26231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
26241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events.DELETED, 1);
26251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
2626192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                    mDb.update("Events", values, "_id=?", selectionArgs);
26271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
26281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
26291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
26301ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
26311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
26321ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
26338f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
263410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
263510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio            scheduleNextAlarm(false /* do not remove alarms */);
263610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio            triggerAppWidgetUpdate(-1 /* changedEventId */);
263710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
26381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
2639192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        // Delete associated data; attendees, however, are deleted with the actual event so
2640192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        // that the sync adapter is able to notify attendees of the cancellation.
2641636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("Instances", "event_id=?", selectionArgs);
2642636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("EventsRawTimes", "event_id=?", selectionArgs);
2643636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("Reminders", "event_id=?", selectionArgs);
2644636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("CalendarAlerts", "event_id=?", selectionArgs);
2645636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("ExtendedProperties", "event_id=?", selectionArgs);
26461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
26471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
26481ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
26497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
26507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
26517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
26527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
26537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
26547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
26557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
26567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
26577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
26587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
26597e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
26607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues values = new ContentValues();
26617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        values.put(Events._SYNC_DIRTY, "1");
26627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
26637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
26647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
26657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
26667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2667636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.delete(table, "_id=?", new String[] {String.valueOf(id)});
2668636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", values, "_id=?", new String[] {String.valueOf(event_id)});
26697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
26707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
26717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
26727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
26737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
26747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
26757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
26767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
26777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
26787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
26797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
26807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
26817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
26827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
26837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
26847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
26857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
26867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
26877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
26887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
26897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
26907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues dirtyValues = new ContentValues();
26917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        dirtyValues.put(Events._SYNC_DIRTY, "1");
26927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
26937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
26947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
26957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
26967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2697636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update(table, values, "_id=?", new String[] {String.valueOf(id)});
2698636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", dirtyValues, "_id=?", new String[] {String.valueOf(event_id)});
26997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
27007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
27017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
27027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
27037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
27047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
27057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
27067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private int deleteMatchingCalendars(String where) {
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
27099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
27119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = mDb.query("Calendars", sCalendarsIdProjection, where,
27137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* selectionArgs */, null /* groupBy */,
27147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27262cc859cab85391a240b9c3f28c935d919c8ceb8cKen Shirriff        return mDb.delete("Calendars", where, null /* whereArgs */);
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: call calculateLastDate()!
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
27319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
27329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String[] selectionArgs) {
2733ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
27349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
27359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
27439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
274568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS
274668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                && match != EVENTS && match != PROVIDER_PROPERTIES) {
27479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throw new IllegalArgumentException(
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    "WHERE based updates not supported");
27499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
27519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
27529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
27539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
2757dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
2758dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
27599323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2760dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2761dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2762dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
27669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27672fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
27682fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
27692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
27729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
27739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2776636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Calendars", values, "_id=?",
2777636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
27789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2779616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa                // The calendar should not be displayed in widget either.
2780616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa                final Integer selected = values.getAsInteger(Calendars.SELECTED);
2781616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa                if (selected != null && selected == 0) {
2782616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa                    triggerAppWidgetUpdate(-1);
2783616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa                }
2784616e6f19fcef87a7dcc910a95c4c340683648ad0Daisuke Miyakawa
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
27889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
27899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = 0;
27917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (match == EVENTS_ID) {
27927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    id = ContentUris.parseId(uri);
2793a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                } else if (callerIsSyncAdapter) {
2794a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    if (selection != null && selection.startsWith("_id=")) {
27957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // The ContentProviderOperation generates an _id=n string instead of
27967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // adding the id to the URL, so parse that out here.
27977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        id = Long.parseLong(selection.substring(4));
2798a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    } else {
2799a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // Sync adapter Events operation affects just Events table, not associated
2800a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // tables.
2801646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (fixAllDayTime(uri, values)) {
280252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            if (Log.isLoggable(TAG, Log.WARN)) {
280352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                Log.w(TAG, "updateInTransaction: Caller is sync adapter. " +
280452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                        "allDay is true but sec, min, hour were not 0.");
280552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                            }
2806646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
2807a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        return mDb.update("Events", values, selection, selectionArgs);
2808a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    }
28097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2810a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    throw new IllegalArgumentException("Unknown URL " + uri);
28117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
28137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
28147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Disallow updating the attendee status in the Events
28169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // table.  In the future, we could support this but we
28179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // would have to query and update the attendees table
28189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // to keep the values consistent.
28199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
28209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
28219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.SELF_ATTENDEE_STATUS
28229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
28239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                // TODO: should we allow this?
28267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (values.containsKey(Events.HTML_URI) && !callerIsSyncAdapter) {
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.HTML_URI
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2831e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
2832e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // TODO: should extend validateEventData to work with updates and call it here
2833e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
283552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
283652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.w(TAG, "Could not update event.");
283752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return 0;
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2840646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we pass in a uri with the id appended to fixAllDayTime
2841646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                Uri allDayUri;
2842646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 1) {
2843646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = ContentUris.withAppendedId(uri, id);
2844646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
2845646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = uri;
2846646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
2847646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(allDayUri, updatedValues)) {
284852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
284952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.w(TAG, "updateInTransaction: " +
285052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
285152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
2852646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
28539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2854636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Events", updatedValues, "_id=?",
2855636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
28569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (result > 0) {
28579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
28589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb);
28599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.DTSTART)) {
28619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // The start time of the event changed, so run the
28629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // event alarm scheduler.
28639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (Log.isLoggable(TAG, Log.DEBUG)) {
28649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Log.d(TAG, "updateInternal() changing event");
28659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
28669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        scheduleNextAlarm(false /* do not remove alarms */);
28679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        triggerAppWidgetUpdate(id);
28689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
28699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
28719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28722fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
28732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28742fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28752fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
28779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
28789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2881636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("Attendees", values, "_id=?",
288283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
28837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
28842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("Attendees", values, uri, null /* selection */,
28852fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
28867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
28892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28922fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
28932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
28949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2895636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.update("CalendarAlerts", values, "_id=?",
2896636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28982fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
28992fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
29002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDb.update("CalendarAlerts", values, selection, selectionArgs);
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29032fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
29042fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
29052fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
29062fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
29077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
29087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2909636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    count = mDb.update("Reminders", values, "_id=?",
291083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
29117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
29122fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    count = updateInTable("Reminders", values, uri, null /* selection */,
29132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
29147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
29157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
29199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
29209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
29227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
29252fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
29262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
29272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
29287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
29297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2930636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("ExtendedProperties", values, "_id=?",
2931636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
29327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
29332fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("ExtendedProperties", values, uri, null /* selection */,
29342fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
29357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
293783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
293883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
293983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
294083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(false);
294183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
294283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
294383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
294483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(true);
294583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
294683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
29479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
294868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            case PROVIDER_PROPERTIES: {
294968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                if (selection == null) {
295068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    throw new UnsupportedOperationException("Selection cannot be null for " + uri);
295168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                }
295268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                if (!selection.equals("key=?")) {
295368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
295468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                }
295568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
295668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
295768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
295868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
295968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
296068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
296168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                }
296268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
296368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
296468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
296568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
296668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // Update the database with the provided values (this call may change the value
296768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // of timezone Instances)
296868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                int result = mDb.update("CalendarCache", values, selection, selectionArgs);
296968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
297068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // if successful, do some house cleaning:
297168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // if the timezone type is set to "home", set the Instances timezone to the previous
297268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // if the timezone type is set to "auto", set the Instances timezone to the current
297368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                //      device one
297468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                // if the timezone Instances is set AND if we are in "home" timezone type, then
297568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                //      save the timezone Instance into "previous" too
297668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                if (result > 0) {
297768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    // If we are changing timezone type...
297868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
297968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
298068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        if (value != null) {
298168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            // if we are setting timezone type to "home"
298268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
298368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                String previousTimezone =
298468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
298568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                if (previousTimezone != null) {
298668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
298768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                }
298868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
298968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
299068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                    regenerateInstancesTable();
299168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                }
299268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            }
299368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            // if we are setting timezone type to "auto"
299468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
299568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
299668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
2997830f982e42eafaeb95b72fef9830167d39b025dcErik                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
2998830f982e42eafaeb95b72fef9830167d39b025dcErik                                    regenerateInstancesTable();
2999830f982e42eafaeb95b72fef9830167d39b025dcErik                                }
300068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            }
300168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        }
300268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    }
300368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    // If we are changing timezone Instances...
300468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
300568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        // if we are in "home" timezone type...
300668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        if (isHomeTimezone()) {
300768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
300868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            // Update the previous value
300968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
301068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
301168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
301268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
301368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                                regenerateInstancesTable();
301468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                            }
301568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                        }
301668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                    }
30171431b9ded6f7a429a6f3c9e737fc79320627409cErik                    triggerAppWidgetUpdate(-1);
301868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                }
301968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                return result;
302068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio            }
302168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
30229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
30239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
30249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3027595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) {
3028595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME);
3029595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE);
3030595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
3031595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere(Calendar.Calendars._SYNC_ACCOUNT + "="
3032595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3033595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
3034595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
3035595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
3036595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere("1"); // I.e. always true
3037595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3038595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3039595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
30409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
3041595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME);
3042595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE);
30439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
30449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            StringBuilder selectionSb = new StringBuilder(Calendar.Calendars._SYNC_ACCOUNT + "="
30459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
30469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
30479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
30489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!TextUtils.isEmpty(selection)) {
30499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(" AND (");
30509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(selection);
30519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(')');
30529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selectionSb.toString();
30549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
30559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
30569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
30609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
30619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
30629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
30639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE,
30649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Calendars.URL, Calendars.SYNC_EVENTS},
30659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
30669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
30679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
30689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
30709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
30719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
3072ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
30739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3074ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
3075ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
3076ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
3077ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
3078ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
3079ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
3080ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
30819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
30829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
30839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30869535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
30879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
308852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
308952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
309052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        + "is empty -- should not happen.");
309152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
30929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
30939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30959535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
30969535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
30979535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
30989535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
30999535627bf6295cd94447beb83e1aac41f50c3600Erik        }
31009535627bf6295cd94447beb83e1aac41f50c3600Erik
31019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
31029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
31039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
31049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
31079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
31089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
31099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: is this needed
31129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    @Override
31139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    public void onSyncStop(SyncContext context, boolean success) {
31149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        super.onSyncStop(context, success);
31159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        if (Log.isLoggable(TAG, Log.DEBUG)) {
31169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            Log.d(TAG, "onSyncStop() success: " + success);
31179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        }
31189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        scheduleNextAlarm(false /* do not remove alarms */);
31199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        triggerAppWidgetUpdate(-1);
31209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    }
31219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
31239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Update any existing widgets with the changed events.
31249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
31259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param changedEventId Specific event known to be changed, otherwise -1.
31269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *            If present, we use it to decide if an update is necessary.
31279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
31289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private synchronized void triggerAppWidgetUpdate(long changedEventId) {
31299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Context context = getContext();
31309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (context != null) {
31319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mAppWidgetProvider.providerUpdated(context, changedEventId);
31329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Retrieve and cache the alarm manager */
31369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager getAlarmManager() {
31379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        synchronized(mAlarmLock) {
31389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (mAlarmManager == null) {
31399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Context context = getContext();
31409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (context == null) {
314152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
314252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.e(TAG, "getAlarmManager() cannot get Context");
314352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
31449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return null;
31459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Object service = context.getSystemService(Context.ALARM_SERVICE);
31479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mAlarmManager = (AlarmManager) service;
31489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return mAlarmManager;
31509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarmCheck(long triggerTime) {
31549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
31559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager == null) {
315652913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
315752913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e(TAG, "scheduleNextAlarmCheck() cannot get AlarmManager");
315852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
31599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
31609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Context context = getContext();
31629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Intent intent = new Intent(CalendarReceiver.SCHEDULE);
31639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        intent.setClass(context, CalendarReceiver.class);
31649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        PendingIntent pending = PendingIntent.getBroadcast(context,
31659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_NO_CREATE);
31669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (pending != null) {
31679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Cancel any previous alarms that do the same thing.
31689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            manager.cancel(pending);
31699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        pending = PendingIntent.getBroadcast(context,
31719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
31729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
31749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
31759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(triggerTime);
31769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
31779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
31789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        manager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
31819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
31849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs the alarm scheduler in a background thread.
31859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
31869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarm(boolean removeAlarms) {
31879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new AlarmScheduler(removeAlarms);
31889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
31899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
31929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread and schedules an alarm for
31939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the next calendar event, if necessary.
31949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
31959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void runScheduleNextAlarm(boolean removeAlarms) {
31969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
31979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.beginTransaction();
31989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
31999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (removeAlarms) {
32009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                removeScheduledAlarmsLocked(db);
32019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
32029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            scheduleNextAlarmLocked(db);
32039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.setTransactionSuccessful();
32049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
32059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.endTransaction();
32069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
32089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
32109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method looks at the 24-hour window from now for any events that it
32119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * needs to schedule.  This method runs within a database transaction. It
32129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * also runs in a background thread.
32139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
32149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The CalendarProvider2 keeps track of which alarms it has already scheduled
32159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid scheduling them more than once and for debugging problems with
32169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms.  It stores this knowledge in a database table called CalendarAlerts
32179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * which persists across reboots.  But the actual alarm list is in memory
32189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and disappears if the phone loses power.  To avoid missing an alarm, we
32199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * clear the entries in the CalendarAlerts table when we start up the
32209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * CalendarProvider2.
32219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
32229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Scheduling an alarm multiple times is not tragic -- we filter out the
32239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * extra ones when we receive them. But we still need to keep track of the
32249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled alarms. The main reason is that we need to prevent multiple
32259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * notifications for the same alarm (on the receive side) in case we
32269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * accidentally schedule the same alarm multiple times.  We don't have
32279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * visibility into the system's alarm list so we can never know for sure if
32289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we have already scheduled an alarm and it's better to err on scheduling
32299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * an alarm twice rather than missing an alarm.  Another reason we keep
32309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * track of scheduled alarms in a database table is that it makes it easy to
32319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * run an SQL query to find the next reminder that we haven't scheduled.
32329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
32339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
32349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
32359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void scheduleNextAlarmLocked(SQLiteDatabase db) {
32369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager alarmManager = getAlarmManager();
32379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (alarmManager == null) {
323852913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
323952913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                Log.e(TAG, "Failed to find the AlarmManager. Could not schedule the next alarm!");
324052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio            }
32419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
32429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long currentMillis = System.currentTimeMillis();
32459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long start = currentMillis - SCHEDULE_ALARM_SLACK;
32469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long end = start + (24 * 60 * 60 * 1000);
32479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentResolver cr = getContext().getContentResolver();
32489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
32499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
32509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(start);
32519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
32529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
32539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32558f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Delete rows in CalendarAlert where the corresponding Instance or
32568f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Reminder no longer exist.
32578f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Also clear old alarms but keep alarms around for a while to prevent
32589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // multiple alerts for the same reminder.  The "clearUpToTime'
32599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // should be further in the past than the point in time where
32609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we start searching for events (the "start" variable defined above).
32618f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        String selectArg[] = new String[] {
32628f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            Long.toString(currentMillis - CLEAR_OLD_ALARM_THRESHOLD)
32638f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        };
32648f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
32658f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        int rowsDeleted =
32668f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            db.delete(CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg);
32679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long nextAlarmTime = end;
32698f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(cr, currentMillis);
32708f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) {
32718f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            nextAlarmTime = tmpAlarmTime;
32729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Extract events from the database sorted by alarm time.  The
32759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // alarm times are computed from Instances.begin (whose units
32769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // are milliseconds) and Reminders.minutes (whose units are
32779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // minutes).
32789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
32799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events whose end time is already in the past.
32809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events alarms that we have already scheduled.
32819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
32829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 1: we can add support for the case where Reminders.minutes
32839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // equals -1 to mean use Calendars.minutes by adding a UNION for
32849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that case where the two halves restrict the WHERE clause on
32859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders.minutes != -1 and Reminders.minutes = 1, respectively.
32869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
32879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 2: we have to name "myAlarmTime" different from the
32889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // "alarmTime" column in CalendarAlerts because otherwise the
32899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query won't find multiple alarms for the same event.
3290156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        //
3291156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // The CAST is needed in the query because otherwise the expression
3292156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will be untyped and sqlite3's manifest typing will not convert the
3293156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // string query parameter to an int in myAlarmtime>=?, so the comparison
3294156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will fail.  This could be simplified if bug 2464440 is resolved.
32959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String query = "SELECT begin-(minutes*60000) AS myAlarmTime,"
32969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " Instances.event_id AS eventId, begin, end,"
32979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " title, allDay, method, minutes"
32989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " FROM Instances INNER JOIN Events"
32999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ON (Events._id = Instances.event_id)"
33009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " INNER JOIN Reminders"
33019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ON (Instances.event_id = Reminders.event_id)"
33029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " WHERE method=" + Reminders.METHOD_ALERT
3303156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime>=CAST(? AS INT)"
3304156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime<=CAST(? AS INT)"
3305156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND end>=?"
33069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND 0=(SELECT count(*) from CalendarAlerts CA"
33079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " where CA.event_id=Instances.event_id AND CA.begin=Instances.begin"
33089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND CA.alarmTime=myAlarmTime)"
33099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ORDER BY myAlarmTime,begin,title";
3310156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        String queryParams[] = new String[] {String.valueOf(start), String.valueOf(nextAlarmTime),
3311156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                String.valueOf(currentMillis)};
33129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
331368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
331468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        boolean isHomeTimezone = mCalendarCache.readTimezoneType().equals(
331568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                CalendarCache.TIMEZONE_TYPE_HOME);
3316d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        acquireInstanceRangeLocked(start,
3317d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                end,
3318d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                false /* don't use minimum expansion windows */,
331968040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                false /* do not force Instances deletion and expansion */,
332068040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                instancesTimezone,
332168040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio                isHomeTimezone);
33229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
33239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
3324156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff            cursor = db.rawQuery(query, queryParams);
33259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33268f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int beginIndex = cursor.getColumnIndex(Instances.BEGIN);
33278f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int endIndex = cursor.getColumnIndex(Instances.END);
33288f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int eventIdIndex = cursor.getColumnIndex("eventId");
33298f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime");
33308f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES);
33319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
33339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time time = new Time();
33349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.set(nextAlarmTime);
33359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
33368f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                Log.d(TAG, "cursor results: " + cursor.getCount() + " nextAlarmTime: "
33378f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        + alarmTimeStr);
33389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (cursor.moveToNext()) {
33419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule all alarms whose alarm time is as early as any
33429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // scheduled alarm.  For example, if the earliest alarm is at
33439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // 1pm, then we will schedule all alarms that occur at 1pm
33449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // but no alarms that occur later than 1pm.
33459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Actually, we allow alarms up to a minute later to also
33469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // be scheduled so that we don't have to check immediately
33479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // again after an event alarm goes off.
33488f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long alarmTime = cursor.getLong(alarmTimeIndex);
33498f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long eventId = cursor.getLong(eventIdIndex);
33508f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final int minutes = cursor.getInt(minutesIndex);
33518f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long startTime = cursor.getLong(beginIndex);
33528f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long endTime = cursor.getLong(endIndex);
33539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
33559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Time time = new Time();
33569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(alarmTime);
33579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
33589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(startTime);
33599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
33608f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
33618f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    Log.d(TAG, "  looking at id: " + eventId + " " + startTime + startTimeStr
33628f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                            + " alarm: " + alarmTime + schedTime);
33639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (alarmTime < nextAlarmTime) {
33669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    nextAlarmTime = alarmTime;
33679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else if (alarmTime >
33681edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff                           nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) {
33699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This event alarm (and all later ones) will be scheduled
33709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // later.
33718f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
33728f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        Log.d(TAG, "This event alarm (and all later ones) will be scheduled later");
33738f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    }
33749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    break;
33759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Avoid an SQLiteContraintException by checking if this alarm
33789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // already exists in the table.
33799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (CalendarAlerts.alarmExists(cr, eventId, startTime, alarmTime)) {
33809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
33819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int titleIndex = cursor.getColumnIndex(Events.TITLE);
33829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        String title = cursor.getString(titleIndex);
33839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.d(TAG, "  alarm exists for id: " + eventId + " " + title);
33849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
33859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
33869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Insert this alarm into the CalendarAlerts table
33899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Uri uri = CalendarAlerts.insert(cr, eventId, startTime,
33909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        endTime, alarmTime, minutes);
33919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (uri == null) {
339252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
339352913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                        Log.e(TAG, "runScheduleNextAlarm() insert into "
339452913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                                + "CalendarAlerts table failed");
339552913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    }
33969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
33979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33998f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                CalendarAlerts.scheduleAlarm(getContext(), alarmManager, alarmTime);
34009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
34029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
34039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
34049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34078f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Refresh notification bar
34088f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (rowsDeleted > 0) {
34098f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            CalendarAlerts.scheduleAlarm(getContext(), alarmManager, currentMillis);
34108f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        }
34118f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
34129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If we scheduled an event alarm, then schedule the next alarm check
34139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for one minute past that alarm.  Otherwise, if there were no
34149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event alarms scheduled, then check again in 24 hours.  If a new
34159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event is inserted before the next alarm check, then this method
34169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // will be run again when the new event is inserted.
34179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (nextAlarmTime != Long.MAX_VALUE) {
34181edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS);
34199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
34201edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS);
34219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
34259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Removes the entries in the CalendarAlerts table for alarms that we have
34269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled but that have not fired yet. We do this to ensure that we
34279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * don't miss an alarm.  The CalendarAlerts table keeps track of the
34289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms that we have scheduled but the actual alarm list is in memory
34299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and will be cleared if the phone reboots.
34309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
34319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We don't need to remove entries that have already fired, and in fact
34329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we should not remove them because we need to display the notifications
34339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * until the user dismisses them.
34349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
34359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We could remove entries that have fired and been dismissed, but we leave
34369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * them around for a while because it makes it easier to debug problems.
34379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Entries that are old enough will be cleaned up later when we schedule
34389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * new alarms.
34399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
34409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void removeScheduledAlarmsLocked(SQLiteDatabase db) {
34419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
34429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "removing scheduled alarms");
34439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.delete(CalendarAlerts.TABLE_NAME,
34459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED, null /* whereArgs */);
34469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sEventsTable = "Events";
34499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sAttendeesTable = "Attendees";
34509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sRemindersTable = "Reminders";
34519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sCalendarAlertsTable = "CalendarAlerts";
34529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sExtendedPropertiesTable = "ExtendedProperties";
34539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
34559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
34569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
34579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int DELETED_EVENTS = 4;
34589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS = 5;
34599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_ID = 6;
34609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES = 7;
34619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES_ID = 8;
34629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS = 9;
34639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS_ID = 10;
34649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES = 11;
34659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES_ID = 12;
34669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS = 13;
34679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_ID = 14;
34689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_BY_INSTANCE = 15;
34696db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int INSTANCES_BY_DAY = 16;
34706db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE = 17;
34716db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE_ID = 18;
34726db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES = 19;
34736db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES_ID = 20;
34746db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_DAYS = 21;
347583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM = 22;
347683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM_REMOVE = 23;
347748587d3291c4db7f0942e1bff55b88cfa7764ba0Erik    private static final int TIME = 24;
347868040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private static final int PROVIDER_PROPERTIES = 25;
34799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
34819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
34829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sEventsProjectionMap;
348319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
34849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
34859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
34869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
348768040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
34889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
3490b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/when/*/*", INSTANCES);
3491b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
3492b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
3493b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events", EVENTS);
3494b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events/#", EVENTS_ID);
3495b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities", EVENT_ENTITIES);
3496b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
3497b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars", CALENDARS);
3498b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars/#", CALENDARS_ID);
3499b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "deleted_events", DELETED_EVENTS);
3500b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees", ATTENDEES);
3501b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees/#", ATTENDEES_ID);
3502b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders", REMINDERS);
3503b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders/#", REMINDERS_ID);
3504b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
3505b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties/#", EXTENDED_PROPERTIES_ID);
3506b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
3507b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
3508b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/by_instance",
3509b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
3510c4e53191b570e09959c5723f4d253977ba48f2d0Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate", SYNCSTATE);
351183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
351283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_PATH, SCHEDULE_ALARM);
351383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
351448587d3291c4db7f0942e1bff55b88cfa7764ba0Erik        sUriMatcher.addURI(Calendar.AUTHORITY, "time/#", TIME);
3515997e2e5cb006682bc1a82441304994b458d9745dErik        sUriMatcher.addURI(Calendar.AUTHORITY, "time", TIME);
351668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "properties", PROVIDER_PROPERTIES);
35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
35199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
35209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HTML_URI, "htmlUri");
35219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TITLE, "title");
35229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
35239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DESCRIPTION, "description");
35249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.STATUS, "eventStatus");
35259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
35269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
35279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTSTART, "dtstart");
35289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTEND, "dtend");
35299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
35309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DURATION, "duration");
35319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ALL_DAY, "allDay");
35329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.VISIBILITY, "visibility");
35339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TRANSPARENCY, "transparency");
35349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
35359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
35369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RRULE, "rrule");
35379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RDATE, "rdate");
35389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXRULE, "exrule");
35399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXDATE, "exdate");
35409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
35419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
35429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
35439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.LAST_DATE, "lastDate");
35449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
35459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
35469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
35479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
35489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
35499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORGANIZER, "organizer");
35507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        sEventsProjectionMap.put(Events.DELETED, "deleted");
35519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3552e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
35531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
35541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
35551ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
35569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
3557982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.COLOR, "color");
3558982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.ACCESS_LEVEL, "access_level");
3559982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.SELECTED, "selected");
35609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.URL, "url");
35619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.TIMEZONE, "timezone");
35629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, "ownerAccount");
35639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3564982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
3565e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
3566e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
3567982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3568e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3569982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
35701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._ID, "_id");
35711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ID, "_sync_id");
35721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_VERSION, "_sync_version");
35731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_TIME, "_sync_time");
3574c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventsProjectionMap.put(Events._SYNC_DATA, "_sync_local_id");
35751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_DIRTY, "_sync_dirty");
35761ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "_sync_account");
35779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE,
35781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                "_sync_account_type");
35799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
358046f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
358119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HTML_URI, "htmlUri");
358219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TITLE, "title");
358319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, "description");
358419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
358519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.STATUS, "eventStatus");
358619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
358719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
358819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTSTART, "dtstart");
358919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTEND, "dtend");
359019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DURATION, "duration");
359119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
359219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ALL_DAY, "allDay");
359319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.VISIBILITY, "visibility");
359419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TRANSPARENCY, "transparency");
359519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
359619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
359719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RRULE, "rrule");
359819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RDATE, "rdate");
359919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXRULE, "exrule");
360019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXDATE, "exdate");
360119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
360219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
360319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
360419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.LAST_DATE, "lastDate");
360519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
360619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
360719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
360819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
360919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
361019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORGANIZER, "organizer");
361119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DELETED, "deleted");
361219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
361319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
3614c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventEntitiesProjectionMap.put(Events._SYNC_DATA, Events._SYNC_DATA);
361519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_VERSION, Events._SYNC_VERSION);
361619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_DIRTY, Events._SYNC_DIRTY);
361719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Calendars.URL, "url");
361819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
36199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
36209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
36219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
36229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
36239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
36249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
36259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
36269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
36279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
36289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
36309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
36319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
36329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
36339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
36349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
36359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
36369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
36379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
36399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
36409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
36419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
36429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
36439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
36459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
36469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
36479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
36489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
36499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
36509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
36519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
365268040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio
365368040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        // CalendarCache columns
365468040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
365568040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
365668040cf2602bb15bb4cf1072cf99132118d5a805Fabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
36579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
36589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
36609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make sure that there are no entries for accounts that no longer
36619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * exist. We are overriding this since we need to delete from the
36629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
36637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
36647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
36659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
36669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
36679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb = mDbHelper.getWritableDatabase();
36689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (mDb == null) return;
36699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
367046f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>();
367146f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
36729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
36739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
36749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
36759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
36779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
36799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
36809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (String table : new String[]{"Calendars"}) {
36829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Find all the accounts the contacts DB knows about, mark the ones that aren't
36839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
36849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Cursor c = mDb.rawQuery("SELECT DISTINCT " + CalendarDatabaseHelper.ACCOUNT_NAME
36859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                        + ","
36869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                        + CalendarDatabaseHelper.ACCOUNT_TYPE + " from "
36879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + table, null);
36889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
36899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (c.getString(0) != null && c.getString(1) != null) {
36909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
36919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
36929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
36939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
36949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
36959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
36969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
36979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
36989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
370052913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
370152913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
370252913fc7cdf91c335e3559e0b38f9b3e2090737fFabrice Di Meglio                }
37039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
37049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mDb.execSQL("DELETE FROM Calendars"
37059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " WHERE " + CalendarDatabaseHelper.ACCOUNT_NAME + "= ? AND "
37069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + CalendarDatabaseHelper.ACCOUNT_TYPE
37079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + "= ?", params);
37089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
37099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
37109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
37119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
37129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
37139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
37149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
37159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3716595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /* package */ static boolean readBooleanQueryParameter(Uri uri, String name,
3717595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            boolean defaultValue) {
3718595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String flag = getQueryParameter(uri, name);
37199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return flag == null
37209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                ? defaultValue
37219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                : (!"false".equals(flag.toLowerCase()) && !"0".equals(flag.toLowerCase()));
37229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
37239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3724595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    // Duplicated from ContactsProvider2.  TODO: a utility class for shared code
3725595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /**
3726595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff     * A fast re-implementation of {@link Uri#getQueryParameter}
3727595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff     */
3728595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /* package */ static String getQueryParameter(Uri uri, String parameter) {
3729595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        String query = uri.getEncodedQuery();
3730595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (query == null) {
3731595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            return null;
3732595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3733595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3734595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int queryLength = query.length();
3735595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int parameterLength = parameter.length();
3736595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3737595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        String value;
3738595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int index = 0;
3739595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        while (true) {
3740595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            index = query.indexOf(parameter, index);
3741595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (index == -1) {
3742595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                return null;
3743595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
3744595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3745595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            index += parameterLength;
3746595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3747595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (queryLength == index) {
3748595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                return null;
3749595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
3750595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3751595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (query.charAt(index) == '=') {
3752595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                index++;
3753595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                break;
3754595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
3755595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3756595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3757595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int ampIndex = query.indexOf('&', index);
3758595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (ampIndex == -1) {
3759595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            value = query.substring(index);
3760595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
3761595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            value = query.substring(index, ampIndex);
3762595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3763595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3764595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        return Uri.decode(value);
3765595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3766636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff
3767636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
3768636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
3769636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
3770636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
3771636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
3772636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
3773636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
3774636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
3775636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
3776636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
3777636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
3778636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
3779636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
3780636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
3781636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
3782636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
3783636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
3784636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
3785636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
37869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
3787