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