CalendarProvider2.java revision b7c010fdc02695b692cd74acf432e8ccb3bda70c
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
207be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
217be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
22370f91c0cfe5a5fecaba6120e703f4d2271d2277Erikimport com.google.common.annotations.VisibleForTesting;
23370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.AlarmManager;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.PendingIntent;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Debug;
44a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
45a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
47f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglioimport android.pim.EventRecurrence;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet;
499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar;
519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Attendees;
529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.CalendarAlerts;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Calendars;
549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Events;
559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Instances;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Reminders;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
581edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
59192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
62ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
65ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
68dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Set;
709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
71dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * is defined in {@link android.provider.Calendar}.
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String TAG = "CalendarProvider2";
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
827be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
837be45683e367bd6897daf6444b03be938f8f5eaaErik
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean PROFILE = false;
859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
868f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
878f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff    private static final String INVALID_CALENDARALERTS_SELECTOR =
88d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang            "_id IN (SELECT ca." + CalendarAlerts._ID + " FROM "
89d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + Tables.CALENDAR_ALERTS + " AS ca"
90d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Tables.INSTANCES
91d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " USING (" + Instances.EVENT_ID + ","
92d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + Instances.BEGIN + "," + Instances.END + ")"
93d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Tables.REMINDERS + " AS r ON"
94d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " (ca." + CalendarAlerts.EVENT_ID + "=r." + Reminders.EVENT_ID
95d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " AND ca." + CalendarAlerts.MINUTES + "=r." + Reminders.MINUTES + ")"
96d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " LEFT OUTER JOIN " + Views.EVENTS + " AS e ON"
97d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " (ca." + CalendarAlerts.EVENT_ID + "=e." + Events._ID + ")"
98d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + " WHERE " + Tables.INSTANCES + "." + Instances.BEGIN + " ISNULL"
99d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR ca." + CalendarAlerts.ALARM_TIME + "<?"
100d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR (r." + Reminders.MINUTES + " ISNULL"
101d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "       AND ca." + CalendarAlerts.MINUTES + "<>0)"
102d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    + "   OR e." + Calendars.SELECTED + "=0)";
1038f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
1041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
1051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
1069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
1089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
1099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
1129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
1167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3;
1177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
1207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
1217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
126646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
127646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
128646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
129646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
130646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
131646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
132646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
133646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
134646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
135646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
136646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
137646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
138646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
139646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
140646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
141646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
147ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
1509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
151315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final Uri SYNCSTATE_CONTENT_URI = Uri.parse("content://syncstate/state");
15283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    //
15383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_URI runs scheduleNextAlarm(false)
15483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // SCHEDULE_ALARM_REMOVE_URI runs scheduleNextAlarm(true)
15583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    // TODO: use a service to schedule alarms rather than private URI
15683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_PATH = "schedule_alarms";
15783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final String SCHEDULE_ALARM_REMOVE_PATH = "schedule_alarms_remove";
15883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_URI =
15983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_PATH);
16083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    /* package */ static final Uri SCHEDULE_ALARM_REMOVE_URI =
16183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            Uri.withAppendedPath(Calendar.CONTENT_URI, SCHEDULE_ALARM_REMOVE_PATH);
1629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // To determine if a recurrence exception originally overlapped the
1649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // window, we need to assume a maximum duration, since we only know
1659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // the original start time.
1669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int MAX_ASSUMED_DURATION = 7*24*60*60*1000;
1679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1688ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
1698ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // Due to an issue in Calendar Server restricting the length of the name we had to strip it down
1708ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1718ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1728ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1738ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1748ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1753443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
176c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.EVENT_ID + ", " +
177c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.DTSTART_2445 + ", " +
178c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.DTEND_2445 + ", " +
1793443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1803443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
1813443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            "EventsRawTimes" + ", " +
1823443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            "Events" +
1833443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
184c2d2953fa4ac4bf9066f40d97858e69e519269f1Fabrice Di Meglio            Calendar.EventsRawTimesColumns.EVENT_ID + " = " + "Events." + Events._ID;
1853443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio
1869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class TimeRange {
1879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public boolean allDay;
1909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesRange {
1939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public InstancesRange(long begin, long end) {
1979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.begin = begin;
1989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.end = end;
1999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesList
2039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends ArrayList<ContentValues> {
2049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class EventInstancesMap
2079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends HashMap<String, InstancesList> {
2081030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        public void add(String syncIdKey, ContentValues values) {
2091030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList instances = get(syncIdKey);
2109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (instances == null) {
2119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                instances = new InstancesList();
2121030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                put(syncIdKey, instances);
2139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instances.add(values);
2159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A thread that runs in the background and schedules the next
2199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // calendar event alarm.
2209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class AlarmScheduler extends Thread {
2219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean mRemoveAlarms;
2229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public AlarmScheduler(boolean removeAlarms) {
2249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mRemoveAlarms = removeAlarms;
2259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
227192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        @Override
2289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
2299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
2309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
2319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                runScheduleNextAlarm(mRemoveAlarms);
2329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (SQLException e) {
233f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
234f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "runScheduleNextAlarm() failed", e);
235f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We search backward in time for event reminders that we may have missed
2429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and schedule them if the event has not yet expired.  The amount in
2439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the past to search backwards is controlled by this constant.  It
2449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should be at least a few minutes to allow for an event that was
2459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * recently created on the web to make its way to the phone.  Two hours
2469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * might seem like overkill, but it is useful in the case where the user
2479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * just crossed into a new timezone and might have just missed an alarm.
2489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2491edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff    private static final long SCHEDULE_ALARM_SLACK = 2 * DateUtils.HOUR_IN_MILLIS;
2509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Alarms older than this threshold will be deleted from the CalendarAlerts
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  This should be at least a day because if the timezone is
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * wrong and the user corrects it we might delete good alarms that
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * appear to be old because the device time was incorrectly in the future.
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This threshold must also be larger than SCHEDULE_ALARM_SLACK.  We add
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the SCHEDULE_ALARM_SLACK to ensure this.
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
2599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * To make it easier to find and debug problems with missed reminders,
2609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * set this to something greater than a day.
2619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long CLEAR_OLD_ALARM_THRESHOLD =
2631edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            7 * DateUtils.DAY_IN_MILLIS + SCHEDULE_ALARM_SLACK;
2649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A lock for synchronizing access to fields that are shared
2669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // with the AlarmScheduler thread.
2679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Object mAlarmLock = new Object();
2689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Allocate the string constant once here instead of on the heap
2789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String CALENDAR_ID_SELECTION = "calendar_id=?";
2799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
28181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
28281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
28381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
28481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
28581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        + Calendar.Instances.EVENT_ID + "=" +
28681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
28781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        + Calendar.Events._ID + ")";
28881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
28918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
29018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
29118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
29218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
29318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
29418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Instances.EVENT_ID + "=" +
29518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
29618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Events._ID + ")" + ") LEFT OUTER JOIN " +
29718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
29818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
29918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Attendees.EVENT_ID + "=" +
30018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
30118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        + Calendar.Events._ID + ")";
30218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
30381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String BETWEEN_DAY_WHERE =
30481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.START_DAY + "<=? AND " +
30581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.END_DAY + ">=?";
30681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
30781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String BETWEEN_WHERE =
30881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.BEGIN + "<=? AND " +
30981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Instances.END + ">=?";
3109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
3129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
3139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
3149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
3159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
3169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
31881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * A regex for describing how we split search queries into tokens.
319dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Keeps quoted phrases as one token.
320dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
321dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *   "one \"two three\"" ==> ["one" "two three"]
322dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
323dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
324dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
325dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
326dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
327dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
328dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
329dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
330dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
331dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
33281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
333dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
334dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
335dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
336dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
337dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
338dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
339dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
340dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
341dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
34281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
34318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
34418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
34518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
34618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
34718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
34818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        "group_concat(" + Calendar.Attendees.ATTENDEE_EMAIL + ")";
34918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
35018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
35118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
35218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
35318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
35418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
35518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        "group_concat(" + Calendar.Attendees.ATTENDEE_NAME + ")";
35618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
35781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
35881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Events.TITLE,
35981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        Calendar.Events.DESCRIPTION,
36018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        Calendar.Events.EVENT_LOCATION,
36118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
36218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
36381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
36481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
3659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager mAlarmManager;
3669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
367a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
368a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
369a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
370a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
371a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
372a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
373a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
374a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
375a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
376a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
377a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
378a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
379dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
380dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
381dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
382dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
383dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
384dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
385a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
386a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
387a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
388a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
389dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
390a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
391a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
392a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
393dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
394dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
395dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
396dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
397dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
398a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
399a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
400a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
401a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
4159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
4199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
4219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
4229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void verifyAccounts() {
4279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
4289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
4299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
4399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
440ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
441ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
442ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
443f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
444f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
445f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
446ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
447ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
448ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
4499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
450ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
451dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
452ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
453ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
4549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
4569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
4579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
4599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Context c = getContext();
4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        c.registerReceiver(mIntentReceiver, filter);
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mMetaData = new MetaData(mDbHelper);
470ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
471ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
472ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        postInitialize();
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
477ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
478ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
479ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
480ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
481ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
482ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
483ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
484ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
485ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
486ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
487ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
488ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
489ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
490ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
491ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
492ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
4939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
4959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
496315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
4979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
4999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
5019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
5049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
5059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
5069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
507ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
5089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
512315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
513315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
514315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
515315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
516315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
517315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
518315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
519315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
5209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
5219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
5229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
523315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
524ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
525315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
526315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
527a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
528315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
529315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
530315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
531ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
532315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
533315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
534ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
535315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
536ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
537ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
538ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
539ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
540ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                rescheduleMissedAlarms();
541ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
542ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
543f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
544f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
545f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
546ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
547ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
548ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
549ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
550ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
551f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
552f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
553f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
554ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
5559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
556ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
557ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
558315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
559ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
560ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
5613443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
562ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
563315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
564ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
565ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
566ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
567ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
568ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
569ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
570ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
5713443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
5723443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
573ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
574ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
575ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
576ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
577ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
5783443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
579f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
580f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
581f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
582f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
583f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
584f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
585f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
586ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
5873443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
588ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
589ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
590ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
591ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
592ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
593ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
594ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
595ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
596ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
597ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
598ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
599f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
600f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
601f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
602ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
603ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
604ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
605ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
606ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
607ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
608f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
609f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
610f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
611ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
612ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
613ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
614ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
615ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
616ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
617ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
618ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
619ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
620ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        values.put("dtstart", get2445ToMillis(timezone, dtStart2445));
621ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        values.put("dtend", get2445ToMillis(timezone, dtEnd2445));
622ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
623dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff        int result = mDb.update("Events", values, "_id=?",
624dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
625ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
626ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
627ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
628ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
629ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
630ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
631ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
632ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
633ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
634ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
635ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
636f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
637f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
638f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
639ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
640ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
6419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
642ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
643ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
644ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
645ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
646315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
647315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
648ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
649ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
650ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
651ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
652ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
65325e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
654ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
655315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
656315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
657ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
659f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
660f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
661f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
662ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
663ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
664ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
665315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
666315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
667315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
668315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
669315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
670ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
6719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
6729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
6739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
6749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
675315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
676315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
6779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
6789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
6799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
6809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
6819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
6821f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
6839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
6849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
6851f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
6861f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
6871f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
6881f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
6891f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
6901f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
691d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    null /* selection */, null /* sort */,
692d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
693315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
694315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
695315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone());
6961f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
6971f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
6981f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
6991f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7001f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
7019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rescheduleMissedAlarms();
7039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void rescheduleMissedAlarms() {
7069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
7079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager != null) {
7089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Context context = getContext();
7099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentResolver cr = context.getContentResolver();
7109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            CalendarAlerts.rescheduleMissedAlarms(cr, context, manager);
7119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
7159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Appends comma separated ids.
7169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param ids Should not be empty
7179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
7189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void appendIds(StringBuilder sb, HashSet<Long> ids) {
7199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (long id : ids) {
7209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            sb.append(id).append(',');
7219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sb.setLength(sb.length() - 1); // Yank the last comma
7249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
727b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
7289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
7299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
7309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        getContext().getContentResolver().notifyChange(Calendar.CONTENT_URI, null,
731b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                syncToNetwork);
7329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
7359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
7369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
737ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
738ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
7399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
7429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
746315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
7479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
7499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
7509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
7551ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
757595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
7601ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
762636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
763636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
7649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
76519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
76619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
76719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
76819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
769595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
77019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
77119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
77219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
77319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
774636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
775636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
77619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
77719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
77943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
7809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
781595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
78443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
7859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
786636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
787636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
7889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
7909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
7919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
7949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
805315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return handleInstanceQuery(qb, begin, end, projection,
807d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                        selection, sortOrder, match == INSTANCES_BY_DAY,
808315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        false /* do not force Instances deletion and expansion */,
809315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
81081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
81181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
81281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
81381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
81481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
81581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
81681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
81781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
81881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
81981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
82081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
82181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
82281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
82381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
824315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
82581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
82681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
82781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                return handleInstanceSearchQuery(qb, begin, end, query, projection,
828315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        selection, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
829315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8306db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
8319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
8329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
8339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
8359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
8379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
8419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
8439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
845315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
846315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
847315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
8491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
8509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
8511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.appendWhere("Events._id=Attendees.event_id");
8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
8541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
8559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
856636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
857636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Attendees._id=?  AND Events._id=Attendees.event_id");
8589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
8609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Reminders");
8619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
8631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Reminders, Events");
8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
865636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
866636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Reminders._id=? AND Events._id=Reminders.event_id");
8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
869e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
871e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
872e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id");
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
875e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
877e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
878e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id");
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
882e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.setTables("CalendarAlerts, " + CalendarDatabaseHelper.Views.EVENTS);
8839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
884636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
885e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                qb.appendWhere(CalendarDatabaseHelper.Views.EVENTS +
886e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff                        "._id=CalendarAlerts.event_id AND CalendarAlerts._id=?");
8879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("ExtendedProperties");
8909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
8927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                qb.setTables("ExtendedProperties");
893636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
894636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("ExtendedProperties._id=?");
8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
896315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
897315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setTables("CalendarCache");
898315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
899315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
9069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
9099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
9109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
911ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
912ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
913ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
914ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
915ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
916ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
917ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
918ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
919ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
9219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
9239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.setNotificationUri(getContext().getContentResolver(), Calendar.Events.CONTENT_URI);
9259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
9349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
940d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
941315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
942315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
9439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
9449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
946d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio            long rangeEnd, String[] projection, String selection, String sort,
947315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean searchByDay, boolean forceExpansion, String instancesTimezone,
948315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
95081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
9519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
9529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
9539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
9549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
955315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
961315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
962315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
96381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            qb.appendWhere(BETWEEN_DAY_WHERE);
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
966315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
967315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
96881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            qb.appendWhere(BETWEEN_WHERE);
9699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9708335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(rangeEnd),
9718335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
9728335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
9737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
97681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
977dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
978dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
979dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
980dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
981dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
982dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
983dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
984dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
985dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
986dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
987dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
98881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
989dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
990dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
99181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
99281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
99381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
99481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
99581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
99681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
997dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
998dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
999dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1000dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1001dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1002dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1003dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1004dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1005dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1006dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1007dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1008dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1009dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1010dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
101181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
101281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
101381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
101481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
101581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
101681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
101781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
101881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
101981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
102081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
102181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
102281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
102381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
102481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
102581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
102681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
102781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
102881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
102981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
103081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * approximation of full-text search across multiple columns.
103181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
103281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
103381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
103481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
103581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
103681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
103781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
103881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
103981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
104081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
104181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
104281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1043dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1044dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1045dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
104681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
104781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
104881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
104981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
105018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
105118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
105218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
105318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
105481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
105581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
105681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
105781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
105881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
105981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
106018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
106118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
106281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
106318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
106418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
106518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
106681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1067f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1068f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
106918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
107081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
107181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
107281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
107381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
107481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
107581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
107681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
1077315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String selection, String sort, boolean searchByDay, String instancesTimezone,
1078315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
107918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
108081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
108181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1082dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
108381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String[] selectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
108418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
108518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1086dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1087dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
108881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
108981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
109081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1091315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
109281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
109381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
109481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
109581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
109681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
109718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
109818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
109956292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
110056292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1101315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1102315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1103315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
110456292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
110581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            qb.appendWhere(BETWEEN_DAY_WHERE);
110681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
110781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
110818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
110918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
111056292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
111156292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1112315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1113315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1114315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
111556292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
111681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            qb.appendWhere(BETWEEN_WHERE);
111781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
111881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
111918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
112018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                Instances._ID /* groupBy */, searchWhere /* having */, sort);
112181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
112281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
11236db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1124315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1125315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
112681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
11276db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
112843556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
112943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1130315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1131192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
113243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
113343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1134192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
113543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1136315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1137315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
113881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.appendWhere(BETWEEN_DAY_WHERE);
11398335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
11408335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
11418335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
11426db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
11439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
11469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
11479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  Acquires the database lock and calls {@link #acquireInstanceRangeLocked}.
11489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
11499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
11509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
11519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1152d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1153315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1154315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
11559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1156d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1157315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1158315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
11599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
11609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1161315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1162315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
11639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
11649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
11659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
11669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
11679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
11709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
11719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
11729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
11739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
11749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
11759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1176315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1177315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1178315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
11799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1180315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1181315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
11829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
11839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
11849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1185315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1186315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1187315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1188315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1189315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
11909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
11919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
11929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
11939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
11949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
11959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
11969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
11979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
11989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
11999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
12009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
12039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
12049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
12059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
12069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
12079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1208315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1209315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1210315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1211315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1212315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1213315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1214315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
12157be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
12167be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
12177be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
12187be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1219315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1220315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1221315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1222d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
12239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
12249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.execSQL("DELETE FROM Instances;");
1225f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
12266db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
12279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
12289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1229315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1230315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1231315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
12329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1233315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
12347be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
12357be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
12367be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
12377be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
12387be45683e367bd6897daf6444b03be938f8f5eaaErik
12397be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1240315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
12417be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
12427be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
12437be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
12447be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1245315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
12469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
12479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
12509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
12519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
12529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
12549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
12559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
12569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
12579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
12589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
12599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
12609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1261f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
12629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
12639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
12649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
12659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
12669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1271315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
12739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1278315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
12799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1283315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
12849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EXPAND_COLUMNS = new String[] {
12879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._ID,
12889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
12899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.STATUS,
12909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTSTART,
12919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTEND,
12929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EVENT_TIMEZONE,
12939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXRULE,
12969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXDATE,
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DURATION,
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ALL_DAY,
12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
13001030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            Events.ORIGINAL_INSTANCE_TIME,
13011dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio            Events.CALENDAR_ID,
13021b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio            Events.DELETED
13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make instances for the given range.
13079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void expandInstanceRangeLocked(long begin, long end, String localTimezone) {
13099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
13119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.startMethodTracing("expandInstanceRangeLocked");
13129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Expanding events between " + begin + " and " + end);
13169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getEntries(begin, end);
13199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            performInstanceExpansion(begin, end, localTimezone, entries);
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.stopMethodTracing();
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Get all entries affecting the given window.
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return Cursor for the entries; caller must close it.
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getEntries(long begin, long end) {
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
13391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
13409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String beginString = String.valueOf(begin);
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String endString = String.valueOf(end);
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // grab recurrence exceptions that fall outside our expansion window but modify
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences that do fall within our window.  we won't insert these into the output
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // set of instances, but instead will just add them to our cancellations list, so we
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // can cancel the correct recurrence expansion instances.
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we don't have originalInstanceDuration or end time.  for now, assume the original
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // instance lasts no longer than 1 week.
13512d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio        // also filter with syncable state (we dont want the entries from a non syncable account)
13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: compute the originalInstanceEndTime or get this from the server.
13532d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio        qb.appendWhere("((dtstart <= ? AND (lastDate IS NULL OR lastDate >= ?)) OR " +
13548335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                "(originalInstanceTime IS NOT NULL AND originalInstanceTime <= ? AND " +
13552d1b3d70a6ebce8194932f8a8355d97a89da113fFabrice Di Meglio                "originalInstanceTime >= ?)) AND (sync_events != 0)");
13568335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {endString, beginString, endString,
13578335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(begin - MAX_ASSUMED_DURATION)};
1358e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        Cursor c = qb.query(mDb, EXPAND_COLUMNS, null /* selection */,
13598335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                selectionArgs, null /* groupBy */,
13607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
1361e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1362e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            Log.v(TAG, "Instance expansion:  got " + c.getCount() + " entries");
1363e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
1364e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        return c;
13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13681030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * Generates a unique key from the syncId and calendarId.
13691030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * The purpose of this is to prevent collisions if two different calendars use the
13701030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * same sync id.  This can happen if a Google calendar is accessed by two different accounts,
13711030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * or with Exchange, where ids are not unique between calendars.
13721030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param syncId Id for the event
13731030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @param calendarId Id for the calendar
13741030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     * @return key
13751030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff     */
13761030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    private String getSyncIdKey(String syncId, long calendarId) {
13771030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        return calendarId + ":" + syncId;
13781030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    }
13791030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
13801030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff    /**
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Perform instance expansion on the given entries.
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param localTimezone
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param entries The entries to process.
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void performInstanceExpansion(long begin, long end, String localTimezone,
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                          Cursor entries) {
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        RecurrenceProcessor rp = new RecurrenceProcessor();
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13911030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // Key into the instance values to hold the original event concatenated with calendar id.
13921030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        final String ORIGINAL_EVENT_AND_CALENDAR = "ORIGINAL_EVENT_AND_CALENDAR";
13931030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int statusColumn = entries.getColumnIndex(Events.STATUS);
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtendColumn = entries.getColumnIndex(Events.DTEND);
13979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
13989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int durationColumn = entries.getColumnIndex(Events.DURATION);
13999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rruleColumn = entries.getColumnIndex(Events.RRULE);
14009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rdateColumn = entries.getColumnIndex(Events.RDATE);
14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exruleColumn = entries.getColumnIndex(Events.EXRULE);
14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exdateColumn = entries.getColumnIndex(Events.EXDATE);
14039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int idColumn = entries.getColumnIndex(Events._ID);
14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
14081030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        int calendarIdColumn = entries.getColumnIndex(Events.CALENDAR_ID);
14091b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        int deletedColumn = entries.getColumnIndex(Events.DELETED);
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues initialValues;
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        EventInstancesMap instancesMap = new EventInstancesMap();
14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Duration duration = new Duration();
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time eventTime = new Time();
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: entries contains all events that affect the current
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // window.  It consists of:
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  These will be
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed.
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Recurrences that included the window.  These will be displayed
14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    if not canceled.
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Recurrence exceptions that fall in the window.  These will be
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed if not cancellations.
14259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    window (subject to 1 week assumption above), but are outside
14279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    the window.  These will not be displayed.  Cases c and d are
14289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    distingushed by the start / end time.
14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        while (entries.moveToNext()) {
14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                initialValues = null;
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                boolean allDay = entries.getInt(allDayColumn) != 0;
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String eventTimezone = entries.getString(eventTimezoneColumn);
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (allDay || TextUtils.isEmpty(eventTimezone)) {
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // in the events table, allDay events start at midnight.
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this forces them to stay at midnight for all day events
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: check that this actually does the right thing.
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTimezone = Time.TIMEZONE_UTC;
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long dtstartMillis = entries.getLong(dtstartColumn);
14459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Long eventId = Long.valueOf(entries.getLong(idColumn));
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String durationStr = entries.getString(durationColumn);
14489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (durationStr != null) {
14499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    try {
14509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.parse(durationStr);
14519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    catch (DateException e) {
1453f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1454f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.w(TAG, "error parsing duration for event "
1455f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + eventId + "'" + durationStr + "'", e);
1456f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.sign = 1;
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.weeks = 0;
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.days = 0;
14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.hours = 0;
14619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.minutes = 0;
14629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.seconds = 0;
14639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        durationStr = "+P0S";
14649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String syncId = entries.getString(syncIdColumn);
14689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String originalEvent = entries.getString(originalEventColumn);
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalInstanceTimeMillis = -1;
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!entries.isNull(originalInstanceTimeColumn)) {
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn);
14739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int status = entries.getInt(statusColumn);
14751dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                boolean deleted = (entries.getInt(deletedColumn) != 0);
14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rruleStr = entries.getString(rruleColumn);
14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rdateStr = entries.getString(rdateColumn);
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exruleStr = entries.getString(exruleColumn);
14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exdateStr = entries.getString(exdateColumn);
14811030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                long calendarId = entries.getLong(calendarIdColumn);
14821030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String syncIdKey = getSyncIdKey(syncId, calendarId); // key into instancesMap
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1484f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                RecurrenceSet recur = null;
1485f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                try {
1486f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr);
1487f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                } catch (EventRecurrence.InvalidFormatException e) {
1488f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
1489f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "Could not parse RRULE recurrence string: " + rruleStr, e);
1490f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
1491f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                    continue;
1492f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                }
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1494f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                if (null != recur && recur.hasRecurrence()) {
14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is repeating
14969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (status == Events.STATUS_CANCELED) {
14989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen!
1499f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1500f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.e(TAG, "Found canceled recurring event in "
1501f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + "Events table.  Ignoring.");
1502f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        continue;
15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
1505370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    if (deleted) {
1506370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                        // Don't expand deleted recurring events
1507370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                        continue;
1508370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    }
15099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // need to parse the event into a local calendar.
15119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.timezone = eventTimezone;
15129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.set(dtstartMillis);
15139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.allDay = allDay;
15149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen.
1517f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        if (Log.isLoggable(TAG, Log.ERROR)) {
1518f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            Log.e(TAG, "Repeating event has no duration -- "
1519f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                    + "should not happen.");
1520f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        }
15219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (allDay) {
15229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // set to one day.
15239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
15249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
15259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 1;
15269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
15279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
15289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.seconds = 0;
15299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            durationStr = "+P1D";
15309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
15319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // compute the duration from dtend, if we can.
15329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // otherwise, use 0s.
15339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
15349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
15359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 0;
15369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
15379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
15389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            if (!entries.isNull(dtendColumn)) {
15399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                long dtendMillis = entries.getLong(dtendColumn);
15409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000);
15419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P" + duration.seconds + "S";
15429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            } else {
15439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = 0;
15449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P0S";
15459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            }
15469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
15479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long[] dates;
15509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    dates = rp.expand(eventTime, recur, begin, end);
15519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Initialize the "eventTime" timezone outside the loop.
15539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This is used in computeTimezoneDependentFields().
15549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
15559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
15569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
15579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
15589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long durationMillis = duration.getMillis();
15619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    for (long date : dates) {
15629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues = new ContentValues();
15639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.EVENT_ID, eventId);
15649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.BEGIN, date);
15669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        long dtendMillis = date + durationMillis;
15679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.END, dtendMillis);
15689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        computeTimezoneDependentFields(date, dtendMillis,
15709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                eventTime, initialValues);
15711030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        instancesMap.add(syncIdKey, initialValues);
15729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else {
15749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is not repeating
15759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues = new ContentValues();
15769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // if this event has an "original" field, then record
15789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // that we need to cancel the original event (we can't
15799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // do that here because the order of this loop isn't
15809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // defined)
15819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (originalEvent != null && originalInstanceTimeMillis != -1) {
15821030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // The ORIGINAL_EVENT_AND_CALENDAR holds the
15831030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // calendar id concatenated with the ORIGINAL_EVENT to form
15841030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        // a unique key, matching the keys for instancesMap.
15851030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                        initialValues.put(ORIGINAL_EVENT_AND_CALENDAR,
15861030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                                getSyncIdKey(originalEvent, calendarId));
15879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.ORIGINAL_INSTANCE_TIME,
15889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                originalInstanceTimeMillis);
15899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.STATUS, status);
15909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
15919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long dtendMillis = dtstartMillis;
15939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
15949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!entries.isNull(dtendColumn)) {
15959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            dtendMillis = entries.getLong(dtendColumn);
15969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
15979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
15989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        dtendMillis = duration.addTo(dtstartMillis);
15999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this non-recurring event might be a recurrence exception that doesn't
16029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // actually fall within our expansion window, but instead was selected
16039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so we can correctly cancel expanded recurrence instances below.  do not
16049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // add events to the instances map if they don't actually fall within our
16059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // expansion window.
16069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if ((dtendMillis < begin) || (dtstartMillis > end)) {
16079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (originalEvent != null && originalInstanceTimeMillis != -1) {
16089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            initialValues.put(Events.STATUS, Events.STATUS_CANCELED);
16099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
1610f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
1611f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                Log.w(TAG, "Unexpected event outside window: " + syncId);
1612f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            }
16139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            continue;
16149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
16159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.EVENT_ID, eventId);
16189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16191dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    initialValues.put(Instances.BEGIN, dtstartMillis);
16209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.END, dtendMillis);
16219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16221dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                    // we temporarily store the DELETED status (will be cleaned later)
16231b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    initialValues.put(Events.DELETED, deleted);
16241dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
16259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
16269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
16279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
16289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
16299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    computeTimezoneDependentFields(dtstartMillis, dtendMillis,
16319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            eventTime, initialValues);
16329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16331030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                    instancesMap.add(syncIdKey, initialValues);
16349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
16359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (DateException e) {
1636f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
1637f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
1638f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
16399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (TimeFormatException e) {
1640f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
1641f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "RecurrenceProcessor error ", e);
1642f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
16439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
16449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains all instances that affect the
16471030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // window, indexed by original sync id concatenated with calendar id.
16481030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        // It consists of:
16499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  They have:
16509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
16519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window.  They may
16529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be subject to exceptions.  They have:
16539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
16549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They have:
16551030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS (since they can
16569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be a modification or cancellation), EVENT_ID, BEGIN, END
16579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
16589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  They have:
16591030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        //   ORIGINAL_EVENT_AND_CALENDAR, ORIGINAL_INSTANCE_TIME, STATUS =
16609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED, EVENT_ID, BEGIN, END
16619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // First, delete the original instances corresponding to recurrence
16639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // exceptions.  We do this by iterating over the list and for each
16649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrence exception, we search the list for an instance with a
16659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // matching "original instance time".  If we find such an instance,
16669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we remove it from the list.  If we don't find such an instance
16679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then we cancel the recurrence exception.
16689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Set<String> keys = instancesMap.keySet();
16691030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
16701030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
16719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
16729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // If this instance is not a recurrence exception, then
16749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // skip it.
16751030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                if (!values.containsKey(ORIGINAL_EVENT_AND_CALENDAR)) {
16769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
16779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
16789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16791030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                String originalEventPlusCalendar = values.getAsString(ORIGINAL_EVENT_AND_CALENDAR);
16809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
16811030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                InstancesList originalList = instancesMap.get(originalEventPlusCalendar);
16829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (originalList == null) {
16839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // The original recurrence is not present, so don't try canceling it.
16849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
16859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
16869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Search the original event for a matching original
16889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance time.  If there is a matching one, then remove
16899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the original one.  We do this both for exceptions that
16909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // change the original instance as well as for exceptions
16919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // that delete the original instance.
16929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                for (int num = originalList.size() - 1; num >= 0; num--) {
16939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    ContentValues originalValues = originalList.get(num);
16949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long beginTime = originalValues.getAsLong(Instances.BEGIN);
16959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (beginTime == originalTime) {
16969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // We found the original instance, so remove it.
16979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        originalList.remove(num);
16989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
16999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains filtered instances.
17049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // It consists of:
17059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.
17069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window and have not
17079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   been subject to exceptions.
17089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They will have
17099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED if they are cancellations.
17109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
17119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  These are STATUS_CANCELED.
17129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do the inserts.  Since the db lock is held when this method is executed,
17149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // this will be done in a transaction.
17159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
17169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // while the calendar app is trying to query the db (expanding instances)), we will
17179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // not be "polite" and yield the lock until we're done.  This will favor local query
17189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // operations over sync/write operations.
17191030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff        for (String syncIdKey : keys) {
17201030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff            InstancesList list = instancesMap.get(syncIdKey);
17219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
17229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17231dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // If this instance was cancelled or deleted then don't create a new
17249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance.
17259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer status = values.getAsInteger(Events.STATUS);
17261b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                boolean deleted = values.containsKey(Events.DELETED) ?
17271b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                        values.getAsBoolean(Events.DELETED) : false;
17281dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                if ((status != null && status == Events.STATUS_CANCELED) || deleted) {
17299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
17309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
17319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17321dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio                // We remove this useless key (not valid in the context of Instances table)
17331b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                values.remove(Events.DELETED);
17341dc2919361bc56af0c5ea763845fae49d289839aFabrice Di Meglio
17359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Remove these fields before inserting a new instance
17361030436aca59c5123ac90e325e8dbd7e04143909Ken Shirriff                values.remove(ORIGINAL_EVENT_AND_CALENDAR);
17379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
17389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.STATUS);
17399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1740c874ed5c6cc0fcc6ac06ae7d20db0eab7d749608Ken Shirriff                mDbHelper.instancesReplace(values);
17419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
17449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
17469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Computes the timezone-dependent fields of an instance of an event and
17479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * updates the "values" map to contain those fields.
17489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
17499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin the start time of the instance (in UTC milliseconds)
17509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end the end time of the instance (in UTC milliseconds)
17519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param local a Time object with the timezone set to the local timezone
17529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values a map that will contain the timezone-dependent fields
17539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
17549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void computeTimezoneDependentFields(long begin, long end,
17559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local, ContentValues values) {
17569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(begin);
17579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startDay = Time.getJulianDay(begin, local.gmtoff);
17589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startMinute = local.hour * 60 + local.minute;
17599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(end);
17619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endDay = Time.getJulianDay(end, local.gmtoff);
17629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endMinute = local.hour * 60 + local.minute;
17639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Special case for midnight, which has endMinute == 0.  Change
17659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that to +24 hours on the previous day to make everything simpler.
17669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Exception: if start and end minute are both 0 on the same day,
17679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then leave endMinute alone.
17689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (endMinute == 0 && endDay > startDay) {
17699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endMinute = 24 * 60;
17709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endDay -= 1;
17719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_DAY, startDay);
17749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_DAY, endDay);
17759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_MINUTE, startMinute);
17769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_MINUTE, endMinute);
17779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
17789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
17809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
17819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
17829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
17839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
17849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
17859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
17869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
17879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
17889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
17899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
17909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
17919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
17929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
17939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
17949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
17959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
17969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
17979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
17989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
17996db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
18009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
180148587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
180248587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1803315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1804315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
18059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
18069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
18079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static boolean isRecurrenceEvent(ContentValues values) {
18119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return (!TextUtils.isEmpty(values.getAsString(Events.RRULE))||
18129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.RDATE))||
18139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT)));
18149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
18159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1816646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1817646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1818646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1819646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1820646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * corrects the fields DTSTART, DTEND, and DURATION if necessary. Also checks to ensure that
1821646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * either both DTSTART and DTEND or DTSTART and DURATION are set for each event.
1822646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1823646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @param updatedValues The values to check and correct
1824646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1825646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1826646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private boolean fixAllDayTime(Uri uri, ContentValues updatedValues) {
1827646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1828646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        if (updatedValues.containsKey(Events.ALL_DAY)
1829646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                && updatedValues.getAsInteger(Events.ALL_DAY).intValue() == 1) {
1830646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtstart = updatedValues.getAsLong(Events.DTSTART);
1831646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtend = updatedValues.getAsLong(Events.DTEND);
1832646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String duration = updatedValues.getAsString(Events.DURATION);
1833646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Time time = new Time();
1834646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Cursor currentTimesCursor = null;
1835646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String tempValue;
1836646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If a complete set of time fields doesn't exist query the db for them. A complete set
1837646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // is dtstart and dtend for non-recurring events or dtstart and duration for recurring
1838646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // events.
1839646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if(dtstart == null || (dtend == null && duration == null)) {
1840646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we have an id to search for, if not this is probably a new event
1841646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 2) {
1842646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    currentTimesCursor = query(uri,
1843646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            ALLDAY_TIME_PROJECTION,
1844646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selection */,
1845646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selectionArgs */,
1846646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* sort */);
1847646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    if (currentTimesCursor != null) {
1848646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (!currentTimesCursor.moveToFirst() ||
1849646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                                currentTimesCursor.getCount() != 1) {
1850646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // Either this is a new event or the query is too general to get data
1851646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // from the db. In either case don't try to use the query and catch
1852646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // errors when trying to update the time fields.
1853646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor.close();
1854646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor = null;
1855646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
1856646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1857646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1858646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1859646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1860646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // Ensure dtstart exists for this event (always required) and set so h,m,s are 0 if
1861646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // necessary.
1862646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // TODO Move this somewhere to check all events, not just allDay events.
1863646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtstart == null) {
1864646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (currentTimesCursor != null) {
1865646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // getLong returns 0 for empty fields, we'd like to know if a field is empty
1866646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // so getString is used instead.
1867646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    tempValue = currentTimesCursor.getString(ALLDAY_DTSTART_INDEX);
1868646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    try {
1869646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        dtstart = Long.valueOf(tempValue);
1870646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    } catch (NumberFormatException e) {
1871646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        currentTimesCursor.close();
1872646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        throw new IllegalArgumentException("Event has no DTSTART field, the db " +
1873646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            "may be damaged. Set DTSTART for this event to fix.");
1874646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1875646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
1876646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    throw new IllegalArgumentException("DTSTART cannot be empty for new events.");
1877646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1878646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1879646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1880646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.set(dtstart.longValue());
1881646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1882646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1883646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1884646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1885646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                updatedValues.put(Events.DTSTART, time.toMillis(true));
1886646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1887646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1888646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1889646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If dtend exists for this event make sure it's h,m,s are 0.
1890646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend == null && currentTimesCursor != null) {
1891646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // getLong returns 0 for empty fields. We'd like to know if a field is empty
1892646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // so getString is used instead.
1893646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                tempValue = currentTimesCursor.getString(ALLDAY_DTEND_INDEX);
1894646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                try {
1895646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = Long.valueOf(tempValue);
1896646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } catch (NumberFormatException e) {
1897646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = null;
1898646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1899646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1900646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend != null) {
1901646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.clear(Time.TIMEZONE_UTC);
1902646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.set(dtend.longValue());
1903646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1904646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.hour = 0;
1905646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.minute = 0;
1906646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.second = 0;
1907646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = time.toMillis(true);
1908646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DTEND, dtend);
1909646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1910646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1911646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1912646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1913646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (currentTimesCursor != null) {
1914646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (duration == null) {
1915646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = currentTimesCursor.getString(ALLDAY_DURATION_INDEX);
1916646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1917646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                currentTimesCursor.close();
1918646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1919646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1920646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration != null) {
1921646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                int len = duration.length();
1922646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1923646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 * in the seconds format, and if so converts it to days.
1924646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 */
1925646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (len == 0) {
1926646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = null;
1927646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else if (duration.charAt(0) == 'P' &&
1928646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        duration.charAt(len - 1) == 'S') {
1929646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int seconds = Integer.parseInt(duration.substring(1, len - 1));
1930646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1931646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = "P" + days + "D";
1932646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DURATION, duration);
1933646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1934646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1935646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1936646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1937646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration == null && dtend == null) {
1938646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
1939646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        "an event.");
1940646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1941646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1942646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1943646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1944646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
19459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
1946b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
1947ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
19489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
19499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
19509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
19529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1953b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final int match = sUriMatcher.match(uri);
19549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
19559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff              case SYNCSTATE:
19569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
19579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
19589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
19597e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
19607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
19617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
19629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
19639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
19649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
19659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
1966e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
1967e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                validateEventData(updatedValues);
1968e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
1969e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
19709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
19719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
19729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
19739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
19749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
19759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
19769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
19779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
19789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
19799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
19809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
19819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
19829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
19839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
19849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
19859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
1986646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(uri, updatedValues)) {
1987f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
1988f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
1989f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
1990f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
1991646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
19929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
19939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
19949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
19959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, true /* new event */, mDb);
19969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
19989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
19999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
20009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
20019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
20029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
20039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
20049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
20059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
20068ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // if the Event Timezone is defined, store it as the original one in the
20078ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // ExtendedProperties table
20088ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    if (values.containsKey(Events.EVENT_TIMEZONE) && !callerIsSyncAdapter) {
20098ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        String originalTimezone = values.getAsString(Events.EVENT_TIMEZONE);
20108ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20118ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        ContentValues expropsValues = new ContentValues();
20128ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.EVENT_ID, id);
20138ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.NAME,
20148ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                EXT_PROP_ORIGINAL_TIMEZONE);
20158ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        expropsValues.put(Calendar.ExtendedProperties.VALUE, originalTimezone);
20168ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20178ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        // Insert the extended property
20188ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        long exPropId = mDbHelper.extendedPropertiesInsert(expropsValues);
20198ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        if (exPropId == -1) {
20208ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
20218ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                Log.e(TAG, "Cannot add the original Timezone in the "
20228ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                        + "ExtendedProperties table for Event: " + id);
20238ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
20248ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        } else {
20258ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            // Update the Event for saying it has some extended properties
20268ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            ContentValues eventValues = new ContentValues();
20278ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            eventValues.put(Events.HAS_EXTENDED_PROPERTIES, "1");
20288ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            int result = mDb.update("Events", eventValues, "_id=?",
20298ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    new String[] {String.valueOf(id)});
20308ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (result <= 0) {
20318ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                if (Log.isLoggable(TAG, Log.ERROR)) {
20328ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    Log.e(TAG, "Cannot update hasExtendedProperties column"
20338ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                            + " for Event: " + id);
20348ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                }
20358ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
20368ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        }
20378ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    }
2038dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
20399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
20429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
20439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
20449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountName = values.getAsString(Calendars._SYNC_ACCOUNT);
20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Calendars._SYNC_ACCOUNT_TYPE);
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
20481b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    String eventsUrl = values.getAsString(Calendars.SYNC1);
20491b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2052dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.attendeesInsert(values);
20607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Attendees.EVENT_ID));
20627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
20659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
20669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.remindersInsert(values);
20737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Reminders.EVENT_ID));
20757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
20829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
20859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
20892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
20902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
20919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Calendar.ExtendedProperties.EVENT_ID)) {
20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
20959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.extendedPropertiesInsert(values);
20987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Calendar.ExtendedProperties.EVENT_ID));
21007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
21039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
21049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
21059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
21096db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2110315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
21117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
21129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
21139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
21149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
21179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
21219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2123e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2124e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Do some validation on event data before inserting.
2125e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * In particular make sure dtend, duration, etc make sense for
2126e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * the type of event (regular, recurrence, exception).  Remove
2127e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * any unexpected fields.
2128e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
2129e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues to insert
2130e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2131e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private void validateEventData(ContentValues values) {
2132e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2133e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2134e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2135e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2136e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT));
2137e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2138e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2139e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2140e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2141e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2142e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2143e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is the recurrence rule
2144e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2145e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2146e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2147e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
2148e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2149e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence: " + values);
2150e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2151e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2152e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_EVENT);
2153e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
2154e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2155e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2156e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2157e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2158e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2159e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2160e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2161e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2162e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2163e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2164e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
2165e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2166e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence exception: " + values);
2167e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2168e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2169e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2170e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2171e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2172e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2173e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2174e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2175e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2176e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2177e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2178e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2179e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
2180e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2181e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for event: " + values);
2182e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2183e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2184e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2185e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2186e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2187e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
21887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private void setEventDirty(int eventId) {
2189636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=?", new Integer[] {eventId});
21907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
21917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
21929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
21939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
21949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
21959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
21969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
21979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2198f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2199f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2200f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2201f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2202f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2203f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
22049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
22059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
22069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
22079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
22089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
22099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
22119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
22129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2214f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2215f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2216f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
22179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
22189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
22209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
22219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
22229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
22239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
22269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
22309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
22319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
22339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
22349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
22369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
22389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
22399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
22409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
22419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
22429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
22439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
22449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
22459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
22479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
22499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
22509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
22519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
22599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
22629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
22639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
22649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
22669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
22679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
22689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
22749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
22759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2276f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2277f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2278f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2298f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2299f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2300f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
23119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
23159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
23169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2342636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        db.update("Events", values, "_id=?", new String[] {String.valueOf(eventId)});
23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the instances table when an event is added or updated.
23479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
23489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param newEvent true if the event is new.
23509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
23519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
23529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateInstancesLocked(ContentValues values,
23539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean newEvent,
23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If there are no expanded Instances, then return.
23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
23599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (fields.maxInstance == 0) {
23609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis == null) {
23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (newEvent) {
23669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // must be present for a new event.
23679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART missing.");
23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2369f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
2370f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Missing DTSTART.  No need to update instance.");
2371f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!newEvent) {
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Want to do this for regular event, recurrence, or exception.
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // For recurrence or exception, more deletion may happen below if we
23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // do an instance expansion.  This deletion will suffice if the exception
23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // is moved outside the window, for instance.
2383636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (isRecurrenceEvent(values))  {
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // The recurrence or exception needs to be (re-)expanded if:
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // a) Exception or recurrence that falls inside window
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean insideWindow = dtstartMillis <= fields.maxInstance &&
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    (lastDateMillis == null || lastDateMillis >= fields.minInstance);
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // b) Exception that affects instance inside window
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // These conditions match the query in getEntries
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            //  See getEntries comment for explanation of subtracting 1 week.
23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean affectsWindow = originalInstanceTime != null &&
23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime <= fields.maxInstance &&
23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
23979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (insideWindow || affectsWindow) {
23989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateRecurrenceInstancesLocked(values, rowId, db);
23999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: an exception creation or update could be optimized by
24019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // updating just the affected instances, instead of regenerating
24029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the recurrence.
24039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
24049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
24079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis == null) {
24089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            dtendMillis = dtstartMillis;
24099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // if the event is in the expanded range, insert
24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // into the instances table.
24139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: deal with durations.  currently, durations are only used in
24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences.
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentValues instanceValues = new ContentValues();
24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.EVENT_ID, rowId);
24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.BEGIN, dtstartMillis);
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.END, dtendMillis);
24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean allDay = false;
24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
24259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                allDay = allDayInteger != 0;
24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Update the timezone-dependent fields.
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local = new Time();
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDay) {
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = Time.TIMEZONE_UTC;
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = fields.timezone;
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.instancesInsert(instanceValues);
24389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Determines the recurrence entries associated with a particular recurrence.
24439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This set is the base recurrence and any exception.
24449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Normally the entries are indicated by the sync id of the base recurrence
24469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * (which is the originalEvent in the exceptions).
24479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * However, a complication is that a recurrence may not yet have a sync id.
24489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * In that case, the recurrence is specified by the rowId.
24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param recurrenceSyncId The sync id of the base recurrence, or null.
24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The row id of the base recurrence.
24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return the relevant entries.
24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) {
24559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
2459636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        String selectionArgs[];
24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
2461636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_id =?";
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2463636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {String.valueOf(rowId)};
24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
2465636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_sync_id = ? OR originalEvent = ?";
24669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
2467636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId};
24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Retrieving events to expand: " + qb.toString());
24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2473636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs,
24747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* groupBy */, null /* having */, null /* sortOrder */);
24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Do incremental Instances update of a recurrence or recurrence exception.
24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method does performInstanceExpansion on just the modified recurrence,
24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid the overhead of recomputing the entire instance table.
24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateRecurrenceInstancesLocked(ContentValues values,
24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
24899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
2491315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
2493315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String recurrenceSyncId;
24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalEvent != null) {
24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = originalEvent;
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the recurrence's sync id from the database
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = DatabaseUtils.stringForQuery(db, "SELECT _sync_id FROM Events"
2499636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    + " WHERE _id=?", new String[] {String.valueOf(rowId)});
25009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrenceSyncId is the _sync_id of the underlying recurrence
25029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the recurrence hasn't gone to the server, it will be null.
25039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Need to clear out old instances
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating updating a recurrence that hasn't gone to the server.
25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Need to delete based on row id
25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
25109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._id =?)";
25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{"" + rowId});
25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating or modifying a recurrence or exception.
25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Delete instances for recurrence (_sync_id = recurrenceSyncId)
25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // and all exceptions (originalEvent = recurrenceSyncId)
25179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
25189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
25199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
25209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._sync_id =?"
25219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " OR Events.originalEvent =?)";
25229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{recurrenceSyncId, recurrenceSyncId});
25239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do instance expansion
25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId);
25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
2528315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            performInstanceExpansion(fields.minInstance, fields.maxInstance, instancesTimezone,
25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                     entries);
25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
25319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
25339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
25389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
25399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
25419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
25429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
25439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
25449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
25459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
25469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
25479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
25499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
25519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
25539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
25549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
25619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
25629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
25639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
25649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
25659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
25669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
25679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
25699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2571f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2572f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2573f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2574f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2575f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2576f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2577f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            values.get(Calendar.Events.RRULE), e);
2578f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2579f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2580f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2582f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
25839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
25849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
25859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
25879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
25899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
25909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
25919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
25939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
25959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
25979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
25989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
26029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
26079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
26089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
26109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2612e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2613e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
2614e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues (in/out)
2615e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2616e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2617e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
26189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
26199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
26209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
26219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
26229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
26259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
26269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2627f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2628f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2629f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rawValues.put("event_id", eventId);
26389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
26459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
26509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
26539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
26549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtstart2445", time.format2445());
26589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
26619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
26629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtend2445", time.format2445());
26649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
26679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("originalInstanceTime2445", time.format2445());
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
26809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
26819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
26829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
26839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("lastDate2445", time.format2445());
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2690b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2691b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2692ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
2701dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
27029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
27039323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2704dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2705dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2706dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2707dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
27121ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                selection = appendAccountToSelection(uri, selection);
27137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
27141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
27151ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                Cursor cursor = mDb.query("Events", ID_ONLY_PROJECTION,
27161ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
27177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
27191ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
27201ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
272110b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
272310b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                    scheduleNextAlarm(false /* do not remove alarms */);
2724dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
27321ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
27331ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
27341ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
27351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
27361ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
27371ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
27381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
273910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
27401ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
27447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Attendees", selection, selectionArgs);
27457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
27467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Attendees", uri, selection, selectionArgs);
27477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
27509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27512fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
27522fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
27532fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
27557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2756636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Attendees", "_id=?", new String[] {String.valueOf(id)});
27577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
27582fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Attendees", uri, null /* selection */,
27592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
27607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
27619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
27657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Reminders", selection, selectionArgs);
27667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
27677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Reminders", uri, selection, selectionArgs);
27687e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
27699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27722fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
27732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
27742fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
27767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2777636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Reminders", "_id=?", new String[] {String.valueOf(id)});
27787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
27792fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Reminders", uri, null /* selection */,
27802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
27812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
27832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
27842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
27852fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
27862fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return mDb.delete("ExtendedProperties", selection, selectionArgs);
27872fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
27882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, selection, selectionArgs);
27892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
27912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
27922fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
27932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
27942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
27952fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
27962fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
27972fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
2798636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("ExtendedProperties", "_id=?",
2799636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
28002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
28012fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, null /* selection */,
28022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
28069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("CalendarAlerts", selection, selectionArgs);
28097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
28107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("CalendarAlerts", uri, selection, selectionArgs);
28117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28182fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
28192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
28209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2821636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.delete("CalendarAlerts", "_id=?", new String[] {String.valueOf(id)});
28229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
28247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
28259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
28269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                StringBuilder selectionSb = new StringBuilder("_id=");
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
2836595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
28377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return deleteMatchingCalendars(selection); // TODO: handle in sync adapter
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
28406db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2841315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
28429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
28439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
28449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
28459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
284810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
28491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
2850192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
28511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
28521ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
28531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        Cursor cursor = mDb.query("Events", EVENTS_PROJECTION,
2854192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                "_id=?", selectionArgs,
2855636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
28561ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
28571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
28581ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
28591ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
28601ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
2861370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                String rRule = cursor.getString(EVENTS_RRULE_INDEX);
2862370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                boolean emptyRRule = TextUtils.isEmpty(rRule);
286348f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
2864370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                if (!emptySyncId && !emptyRRule) {
2865370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    // Delete exceptions to this event as well.
2866370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik                    mDb.delete("Events", "originalEvent=?", new String[] {syncId});
28671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
28681ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
28691ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
28701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
28711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
28721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
28731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
28741ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX);
28751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)
28761ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        || !TextUtils.isEmpty(origEvent)) {
28771ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
28781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
28791ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
288048f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
288148f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
288248f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
2883192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                    mDb.delete("Events", "_id=?", selectionArgs);
28841ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
28851ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
28861b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
28871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
2888192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank                    mDb.update("Events", values, "_id=?", selectionArgs);
288902494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
289043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
289143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
289202494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio                    mDb.delete("Instances", "event_id=?", selectionArgs);
289302494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio                    mDb.delete("EventsRawTimes", "event_id=?", selectionArgs);
289402494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio                    mDb.delete("Reminders", "event_id=?", selectionArgs);
289502494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio                    mDb.delete("CalendarAlerts", "event_id=?", selectionArgs);
289602494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio                    mDb.delete("ExtendedProperties", "event_id=?", selectionArgs);
28971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
28981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
28991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
29001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
29011ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
29021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
29038f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
290410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
290510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio            scheduleNextAlarm(false /* do not remove alarms */);
2906dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
290710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
29081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
29091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
29101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
29127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
29137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
29147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
29157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
29167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
29177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
29187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
29197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
29207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
29217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
29227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues values = new ContentValues();
29237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        values.put(Events._SYNC_DIRTY, "1");
29247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
29257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
29267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
29277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
29287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2929636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.delete(table, "_id=?", new String[] {String.valueOf(id)});
2930636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", values, "_id=?", new String[] {String.valueOf(event_id)});
29317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
29327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
29337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
29347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
29357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
29367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
29377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
29387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
29397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
29407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
29417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
29427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
29437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
29447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
29457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
29467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
29477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
29487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
29497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
29507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
29517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
29527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues dirtyValues = new ContentValues();
29537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        dirtyValues.put(Events._SYNC_DIRTY, "1");
29547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
29557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
29567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
29577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
29587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2959636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update(table, values, "_id=?", new String[] {String.valueOf(id)});
2960636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", dirtyValues, "_id=?", new String[] {String.valueOf(event_id)});
29617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
29627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
29637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
29647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
29657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
29667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
29677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
29687e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private int deleteMatchingCalendars(String where) {
29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
29719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
29729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
29739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = mDb.query("Calendars", sCalendarsIdProjection, where,
29757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* selectionArgs */, null /* groupBy */,
29767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
29779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
29789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
29799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
29819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
29829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
29839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
29849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
29869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
29879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29882cc859cab85391a240b9c3f28c935d919c8ceb8cKen Shirriff        return mDb.delete("Calendars", where, null /* whereArgs */);
29899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: call calculateLastDate()!
29929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
29939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
2994b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
2995ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
29969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
29979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
30009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
30019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
300343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS
3004315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                && match != EVENTS && match != CALENDARS && match != PROVIDER_PROPERTIES) {
30059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throw new IllegalArgumentException(
30069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    "WHERE based updates not supported");
30079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
30099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
30109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
30119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
30129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
30149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
3015dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                String selectionWithId = (BaseColumns._ID + "=?")
3016dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
30179323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3018dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3019dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3020dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
30219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
302343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
30249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
30259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
302643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
302743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
302843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null) {
302943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        throw new UnsupportedOperationException("Selection not permitted for "
303043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                                + uri);
303143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
303243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
303343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
303443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
303543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
303643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
303743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
303843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
303943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null && selection.startsWith("_id=")) {
304043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
304143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
304243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
304343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
304443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        return mDb.update("Calendars", values, selection, selectionArgs);
304543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
304643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
304743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
304843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    values.put(Calendars._SYNC_DIRTY, 1);
30492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
30509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
30519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
30529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
30539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
30549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3055636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Calendars", values, "_id=?",
3056636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30583ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
3059d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
3060d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    if (values.containsKey(Calendars.SELECTED)) {
3061d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
3062d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
3063d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
3064d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
3065d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        scheduleNextAlarm(false);
3066d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
30673ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
3068dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
30693ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
30703ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
30719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
30729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
30749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
30759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = 0;
30777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (match == EVENTS_ID) {
30787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    id = ContentUris.parseId(uri);
3079a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                } else if (callerIsSyncAdapter) {
308043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: same remark as for CALENDARS/CALENDARS_ID case as this is not
308143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // sufficient to deal with all the "_id" case in selection
3082a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    if (selection != null && selection.startsWith("_id=")) {
30837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // The ContentProviderOperation generates an _id=n string instead of
30847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // adding the id to the URL, so parse that out here.
30857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        id = Long.parseLong(selection.substring(4));
3086a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    } else {
3087a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // Sync adapter Events operation affects just Events table, not associated
3088a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // tables.
3089646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (fixAllDayTime(uri, values)) {
3090f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.WARN)) {
3091f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                Log.w(TAG, "updateInTransaction: Caller is sync adapter. " +
3092f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                        "allDay is true but sec, min, hour were not 0.");
3093f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            }
3094646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
3095a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        return mDb.update("Events", values, selection, selectionArgs);
3096a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    }
30977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3098a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    throw new IllegalArgumentException("Unknown URL " + uri);
30997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
31017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
31027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Disallow updating the attendee status in the Events
31049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // table.  In the future, we could support this but we
31059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // would have to query and update the attendees table
31069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // to keep the values consistent.
31079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
31089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
31099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.SELF_ATTENDEE_STATUS
31109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
31119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                // TODO: should we allow this?
31147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (values.containsKey(Events.HTML_URI) && !callerIsSyncAdapter) {
31159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
31169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.HTML_URI
31179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
31189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3119e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
3120e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // TODO: should extend validateEventData to work with updates and call it here
3121e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
31229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
3123f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3124f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "Could not update event.");
3125f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
31269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return 0;
31279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3128646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we pass in a uri with the id appended to fixAllDayTime
3129646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                Uri allDayUri;
3130646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 1) {
3131646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = ContentUris.withAppendedId(uri, id);
3132646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
3133646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = uri;
3134646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
3135646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(allDayUri, updatedValues)) {
3136f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3137f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "updateInTransaction: " +
3138f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
3139f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
3140646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
31419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3142636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Events", updatedValues, "_id=?",
3143636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
31449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (result > 0) {
31459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
31469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb);
31479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.DTSTART)) {
31499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // The start time of the event changed, so run the
31509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // event alarm scheduler.
31519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (Log.isLoggable(TAG, Log.DEBUG)) {
31529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Log.d(TAG, "updateInternal() changing event");
31539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
31549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        scheduleNextAlarm(false /* do not remove alarms */);
31559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
31563ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3157dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
31589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31593ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
31609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
31619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
31632fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31642fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31652fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
31679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
31689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3171636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("Attendees", values, "_id=?",
317283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
31737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
31742fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("Attendees", values, uri, null /* selection */,
31752fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
31767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31782fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
31792fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
31832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
31849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3185636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.update("CalendarAlerts", values, "_id=?",
3186636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
31879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
31892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
31902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
31919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDb.update("CalendarAlerts", values, selection, selectionArgs);
31929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
31942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31952fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31962fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3199636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    count = mDb.update("Reminders", values, "_id=?",
320083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
32017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
32022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    count = updateInTable("Reminders", values, uri, null /* selection */,
32032fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
32047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
32057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
32069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
32079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
32089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
32099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
32109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
32119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
32127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
32139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
32142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
32152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
32162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
32172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
32187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
32197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3220636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("ExtendedProperties", values, "_id=?",
3221636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
32227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
32232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("ExtendedProperties", values, uri, null /* selection */,
32242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
32257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
32269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
322783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
322883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
322983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
323083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(false);
323183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
323283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
323383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
323483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                scheduleNextAlarm(true);
323583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
323683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
32379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3238315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
3239315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (selection == null) {
3240315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection cannot be null for " + uri);
3241315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3242315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
3243315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
3244315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3245315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3246315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
3247315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3248315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
3249315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
3250315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
3251315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3252315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3253315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
3254315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
3255315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3256315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
3257315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
3258315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                int result = mDb.update("CalendarCache", values, selection, selectionArgs);
3259315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3260315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
3261315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone type is set to "home", set the Instances timezone to the previous
3262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone type is set to "auto", set the Instances timezone to the current
3263315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                //      device one
3264315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if the timezone Instances is set AND if we are in "home" timezone type, then
3265315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                //      save the timezone Instance into "previous" too
3266315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
3267315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
3268315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
3269315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
3270315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
3271315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
3272315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
3273315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
3274315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
3275315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
3276315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
3277315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3278315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
3279d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
3280315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
3281315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3282d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3283315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3284315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3285315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
3286315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
3287315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
3288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
3289315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
3290315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3291d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3292315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3293315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3294315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3295315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3296315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
3297315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
3298315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
3299315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
3300315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
3301315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
3302315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
3303315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
3304d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
3305315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
3306315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
3307315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
3308d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
3309315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3310315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3311315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3312315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3313315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
3314315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
3315315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
33169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
33179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
33189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
33209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3321595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) {
3322b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3323b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_NAME);
3324b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3325b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_TYPE);
3326595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
3327595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere(Calendar.Calendars._SYNC_ACCOUNT + "="
3328595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3329595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
3330595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
3331595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
3332595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere("1"); // I.e. always true
3333595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3334595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3335595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
33369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
3337b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3338b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_NAME);
3339b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3340b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio                Calendar.EventsEntity.ACCOUNT_TYPE);
33419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
33429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            StringBuilder selectionSb = new StringBuilder(Calendar.Calendars._SYNC_ACCOUNT + "="
33439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
33449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
33459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
33469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!TextUtils.isEmpty(selection)) {
33479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(" AND (");
33489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(selection);
33499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(')');
33509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selectionSb.toString();
33529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
33539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
33549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
33569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
33589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
33599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
33609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
33619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE,
33621b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                        Calendars.SYNC1, Calendars.SYNC_EVENTS},
33639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
33649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
33659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
33669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
33689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
33699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
3370ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
33719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3372ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
3373ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
3374ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
3375ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
3376ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
3377ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
3378ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
33799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
33809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
33819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33849535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
33859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
3386f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3387f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
3388f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
3389f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
33909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
33919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33939535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
33949535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
33959535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
33969535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
33979535627bf6295cd94447beb83e1aac41f50c3600Erik        }
33989535627bf6295cd94447beb83e1aac41f50c3600Erik
33999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
34009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
34019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
34029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
34059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
34069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
34079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: is this needed
34109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    @Override
34119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    public void onSyncStop(SyncContext context, boolean success) {
34129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        super.onSyncStop(context, success);
34139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        if (Log.isLoggable(TAG, Log.DEBUG)) {
34149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            Log.d(TAG, "onSyncStop() success: " + success);
34159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        }
34169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        scheduleNextAlarm(false /* do not remove alarms */);
34179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        triggerAppWidgetUpdate(-1);
34189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    }
34199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Retrieve and cache the alarm manager */
34219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager getAlarmManager() {
34229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        synchronized(mAlarmLock) {
34239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (mAlarmManager == null) {
34249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Context context = getContext();
34259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (context == null) {
3426f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
3427f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "getAlarmManager() cannot get Context");
3428f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
34299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return null;
34309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
34319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Object service = context.getSystemService(Context.ALARM_SERVICE);
34329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mAlarmManager = (AlarmManager) service;
34339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return mAlarmManager;
34359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarmCheck(long triggerTime) {
34399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
34409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager == null) {
3441f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
3442f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "scheduleNextAlarmCheck() cannot get AlarmManager");
3443f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
34449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
34459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Context context = getContext();
34479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Intent intent = new Intent(CalendarReceiver.SCHEDULE);
34489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        intent.setClass(context, CalendarReceiver.class);
34499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        PendingIntent pending = PendingIntent.getBroadcast(context,
34509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_NO_CREATE);
34519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (pending != null) {
34529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Cancel any previous alarms that do the same thing.
34539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            manager.cancel(pending);
34549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        pending = PendingIntent.getBroadcast(context,
34569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
34579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
34599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
34609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(triggerTime);
34619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
34629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
34639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        manager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
34669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
34699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs the alarm scheduler in a background thread.
34709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
34719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarm(boolean removeAlarms) {
34729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new AlarmScheduler(removeAlarms);
34739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
34749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
34779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread and schedules an alarm for
34789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the next calendar event, if necessary.
34799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
34809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void runScheduleNextAlarm(boolean removeAlarms) {
34819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
34829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.beginTransaction();
34839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
34849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (removeAlarms) {
34859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                removeScheduledAlarmsLocked(db);
34869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            scheduleNextAlarmLocked(db);
34889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.setTransactionSuccessful();
34899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
34909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.endTransaction();
34919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
34939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
34959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method looks at the 24-hour window from now for any events that it
34969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * needs to schedule.  This method runs within a database transaction. It
34979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * also runs in a background thread.
34989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
34999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The CalendarProvider2 keeps track of which alarms it has already scheduled
35009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid scheduling them more than once and for debugging problems with
35019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms.  It stores this knowledge in a database table called CalendarAlerts
35029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * which persists across reboots.  But the actual alarm list is in memory
35039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and disappears if the phone loses power.  To avoid missing an alarm, we
35049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * clear the entries in the CalendarAlerts table when we start up the
35059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * CalendarProvider2.
35069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
35079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Scheduling an alarm multiple times is not tragic -- we filter out the
35089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * extra ones when we receive them. But we still need to keep track of the
35099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled alarms. The main reason is that we need to prevent multiple
35109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * notifications for the same alarm (on the receive side) in case we
35119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * accidentally schedule the same alarm multiple times.  We don't have
35129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * visibility into the system's alarm list so we can never know for sure if
35139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we have already scheduled an alarm and it's better to err on scheduling
35149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * an alarm twice rather than missing an alarm.  Another reason we keep
35159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * track of scheduled alarms in a database table is that it makes it easy to
35169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * run an SQL query to find the next reminder that we haven't scheduled.
35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
35189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
35199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
35209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void scheduleNextAlarmLocked(SQLiteDatabase db) {
3521b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        Time time = new Time();
35229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager alarmManager = getAlarmManager();
35239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (alarmManager == null) {
3524f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
3525f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Failed to find the AlarmManager. Could not schedule the next alarm!");
3526f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
35279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
35289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long currentMillis = System.currentTimeMillis();
35319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long start = currentMillis - SCHEDULE_ALARM_SLACK;
35329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long end = start + (24 * 60 * 60 * 1000);
35339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentResolver cr = getContext().getContentResolver();
35349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
35359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(start);
35369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
35379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
35389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35408f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Delete rows in CalendarAlert where the corresponding Instance or
35418f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Reminder no longer exist.
35428f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Also clear old alarms but keep alarms around for a while to prevent
35439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // multiple alerts for the same reminder.  The "clearUpToTime'
35449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // should be further in the past than the point in time where
35459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we start searching for events (the "start" variable defined above).
35468f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        String selectArg[] = new String[] {
35478f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            Long.toString(currentMillis - CLEAR_OLD_ALARM_THRESHOLD)
35488f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        };
35498f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
35508f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        int rowsDeleted =
35518f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            db.delete(CalendarAlerts.TABLE_NAME, INVALID_CALENDARALERTS_SELECTOR, selectArg);
35529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long nextAlarmTime = end;
35548f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        final long tmpAlarmTime = CalendarAlerts.findNextAlarmTime(cr, currentMillis);
35558f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (tmpAlarmTime != -1 && tmpAlarmTime < nextAlarmTime) {
35568f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            nextAlarmTime = tmpAlarmTime;
35579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Extract events from the database sorted by alarm time.  The
35609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // alarm times are computed from Instances.begin (whose units
35619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // are milliseconds) and Reminders.minutes (whose units are
35629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // minutes).
35639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
35649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events whose end time is already in the past.
35659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events alarms that we have already scheduled.
35669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
35679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 1: we can add support for the case where Reminders.minutes
35689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // equals -1 to mean use Calendars.minutes by adding a UNION for
35699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that case where the two halves restrict the WHERE clause on
35709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders.minutes != -1 and Reminders.minutes = 1, respectively.
35719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
35729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 2: we have to name "myAlarmTime" different from the
35739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // "alarmTime" column in CalendarAlerts because otherwise the
35749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query won't find multiple alarms for the same event.
3575156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        //
3576156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // The CAST is needed in the query because otherwise the expression
3577156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will be untyped and sqlite3's manifest typing will not convert the
3578156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // string query parameter to an int in myAlarmtime>=?, so the comparison
3579156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff        // will fail.  This could be simplified if bug 2464440 is resolved.
3580b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3581b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        time.setToNow();
3582b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        time.normalize(false);
3583b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        long localOffset = time.gmtoff * 1000;
3584b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3585b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String allDayOffset = " -(" + localOffset + ") ";
3586b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String subQueryPrefix = "SELECT " + Instances.BEGIN;
3587b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String subQuerySuffix = " -(" + Reminders.MINUTES + "*" +
3588b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + DateUtils.MINUTE_IN_MILLIS + ")"
3589b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AS myAlarmTime" + "," + Tables.INSTANCES
3590d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "." + Instances.EVENT_ID + " AS eventId"
3591d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Instances.BEGIN + "," + Instances.END
3592d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Instances.TITLE + "," + Instances.ALL_DAY
3593d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "," + Reminders.METHOD + "," + Reminders.MINUTES
3594d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " FROM " + Tables.INSTANCES
3595d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " INNER JOIN " + Views.EVENTS
3596d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ON (" + Views.EVENTS + "." + Events._ID
3597d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "=" + Tables.INSTANCES + "." + Instances.EVENT_ID + ")"
3598d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " INNER JOIN " + Tables.REMINDERS
3599d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ON (" + Tables.INSTANCES + "." + Instances.EVENT_ID
3600d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID + ")"
3601d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " WHERE " + Calendars.SELECTED + "=1"
3602156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime>=CAST(? AS INT)"
3603156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff                + " AND myAlarmTime<=CAST(? AS INT)"
3604d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " AND " + Instances.END + ">=?"
3605b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Reminders.METHOD + "=" + Reminders.METHOD_ALERT;
3606b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3607b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we query separately for all day events to convert to local time from UTC
3608b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we need to /subtract/ the offset to get the correct resulting local time
3609b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String allDayQuery = subQueryPrefix + allDayOffset + subQuerySuffix
3610b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Instances.ALL_DAY + "=1";
3611b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String nonAllDayQuery = subQueryPrefix + subQuerySuffix
3612b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " AND " + Instances.ALL_DAY + "=0";
3613b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3614b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // we use UNION ALL because we are guaranteed to have no dupes between
3615b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // the two queries, and it is less expensive
3616b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String query = "SELECT *"
3617b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " FROM (" + allDayQuery + " UNION ALL " + nonAllDayQuery + ")"
3618b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                // avoid rescheduling existing alarms
3619b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                + " WHERE 0=(SELECT count(*) from " + Tables.CALENDAR_ALERTS + " CA"
3620b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " WHERE CA." + CalendarAlerts.EVENT_ID + "=eventId"
3621b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " AND CA." + CalendarAlerts.BEGIN + "=" + Instances.BEGIN
3622b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                         + " AND CA." + CalendarAlerts.ALARM_TIME + "=myAlarmTime)"
3623d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                + " ORDER BY myAlarmTime," + Instances.BEGIN + "," + Instances.TITLE;
36249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3625b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        String queryParams[] = new String[] { String.valueOf(start), String.valueOf(nextAlarmTime),
3626b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                String.valueOf(currentMillis), String.valueOf(start), String.valueOf(nextAlarmTime),
3627b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                String.valueOf(currentMillis) };
3628b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang
3629315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
3630315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean isHomeTimezone = mCalendarCache.readTimezoneType().equals(
3631315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                CalendarCache.TIMEZONE_TYPE_HOME);
3632b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        // expand this range by a day on either end to account for all day events
3633b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang        acquireInstanceRangeLocked(start - DateUtils.DAY_IN_MILLIS,
3634b222a85a892be92fe380c36abeaea79aa8f160ddMason Tang                end + DateUtils.DAY_IN_MILLIS,
3635d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                false /* don't use minimum expansion windows */,
3636315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances deletion and expansion */,
3637315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone,
3638315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                isHomeTimezone);
36399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
36409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
3641156ad29fe71eaae73cddad9b17690d1cc8225136Ken Shirriff            cursor = db.rawQuery(query, queryParams);
36429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36438f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int beginIndex = cursor.getColumnIndex(Instances.BEGIN);
36448f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int endIndex = cursor.getColumnIndex(Instances.END);
36458f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int eventIdIndex = cursor.getColumnIndex("eventId");
36468f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime");
36478f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            final int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES);
36489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
36509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.set(nextAlarmTime);
36519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
36528f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                Log.d(TAG, "cursor results: " + cursor.getCount() + " nextAlarmTime: "
36538f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        + alarmTimeStr);
36549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
36559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (cursor.moveToNext()) {
36579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule all alarms whose alarm time is as early as any
36589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // scheduled alarm.  For example, if the earliest alarm is at
36599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // 1pm, then we will schedule all alarms that occur at 1pm
36609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // but no alarms that occur later than 1pm.
36619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Actually, we allow alarms up to a minute later to also
36629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // be scheduled so that we don't have to check immediately
36639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // again after an event alarm goes off.
36648f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long alarmTime = cursor.getLong(alarmTimeIndex);
36658f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long eventId = cursor.getLong(eventIdIndex);
36668f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final int minutes = cursor.getInt(minutesIndex);
36678f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long startTime = cursor.getLong(beginIndex);
36688f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                final long endTime = cursor.getLong(endIndex);
36699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
36719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(alarmTime);
36729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
36739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(startTime);
36749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
36758f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
36768f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    Log.d(TAG, "  looking at id: " + eventId + " " + startTime + startTimeStr
36778f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                            + " alarm: " + alarmTime + schedTime);
36789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
36799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (alarmTime < nextAlarmTime) {
36819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    nextAlarmTime = alarmTime;
36829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else if (alarmTime >
36831edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff                           nextAlarmTime + DateUtils.MINUTE_IN_MILLIS) {
36849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This event alarm (and all later ones) will be scheduled
36859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // later.
36868f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
36878f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                        Log.d(TAG, "This event alarm (and all later ones) will be scheduled later");
36888f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                    }
36899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    break;
36909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
36919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Avoid an SQLiteContraintException by checking if this alarm
36939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // already exists in the table.
36949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (CalendarAlerts.alarmExists(cr, eventId, startTime, alarmTime)) {
36959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
36969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int titleIndex = cursor.getColumnIndex(Events.TITLE);
36979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        String title = cursor.getString(titleIndex);
36989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.d(TAG, "  alarm exists for id: " + eventId + " " + title);
36999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
37009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
37019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
37029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Insert this alarm into the CalendarAlerts table
37049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Uri uri = CalendarAlerts.insert(cr, eventId, startTime,
37059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        endTime, alarmTime, minutes);
37069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (uri == null) {
3707f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
3708f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "runScheduleNextAlarm() insert into "
3709f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "CalendarAlerts table failed");
3710f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
37119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
37129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
37139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37148f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff                CalendarAlerts.scheduleAlarm(getContext(), alarmManager, alarmTime);
37159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
37169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
37179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
37189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
37199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
37209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
37219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37228f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        // Refresh notification bar
37238f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        if (rowsDeleted > 0) {
37248f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff            CalendarAlerts.scheduleAlarm(getContext(), alarmManager, currentMillis);
37258f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff        }
37268f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
37279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If we scheduled an event alarm, then schedule the next alarm check
37289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for one minute past that alarm.  Otherwise, if there were no
37299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event alarms scheduled, then check again in 24 hours.  If a new
37309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event is inserted before the next alarm check, then this method
37319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // will be run again when the new event is inserted.
37329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (nextAlarmTime != Long.MAX_VALUE) {
37331edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(nextAlarmTime + DateUtils.MINUTE_IN_MILLIS);
37349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
37351edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriff            scheduleNextAlarmCheck(currentMillis + DateUtils.DAY_IN_MILLIS);
37369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
37379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
37389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
37409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Removes the entries in the CalendarAlerts table for alarms that we have
37419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled but that have not fired yet. We do this to ensure that we
37429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * don't miss an alarm.  The CalendarAlerts table keeps track of the
37439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms that we have scheduled but the actual alarm list is in memory
37449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and will be cleared if the phone reboots.
37459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
37469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We don't need to remove entries that have already fired, and in fact
37479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we should not remove them because we need to display the notifications
37489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * until the user dismisses them.
37499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
37509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We could remove entries that have fired and been dismissed, but we leave
37519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * them around for a while because it makes it easier to debug problems.
37529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Entries that are old enough will be cleaned up later when we schedule
37539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * new alarms.
37549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
37559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void removeScheduledAlarmsLocked(SQLiteDatabase db) {
37569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
37579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "removing scheduled alarms");
37589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
37599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.delete(CalendarAlerts.TABLE_NAME,
37609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED, null /* whereArgs */);
37619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
37629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3763a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3764a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3765a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3766a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
3767dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
3768dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     * @param whether or not the update is being triggered by a sync
3769a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3770dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
3771dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
3772dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
3773a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3774a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3775a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3776a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3777a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3778a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
3779a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
3780a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
3781a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3782a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
3783a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3784a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * @param the ID of the event that changed, or -1 for no specific event
3785dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     * @param whether or not the update is being triggered by a sync
3786a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3787dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
3788dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
3789a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
3790a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
3791a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
3792a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
3793a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
3794dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
3795dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
3796dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
3797dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
3798dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
3799dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
3800dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
3801dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
3802dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
3803dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
3804dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
3805dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
3806dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
3807dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
3808dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
3809dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
3810dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
3811a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
3812dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
3813a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3814a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3815a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3816a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
3817a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
3818a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #sendUpdateNotification()} instead.
3819a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3820a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * @see #sendUpdateNotification()
3821a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3822a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
3823a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
3824dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                Calendar.CONTENT_URI);
3825f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
3826f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
3827f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
3828a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        getContext().sendBroadcast(intent, null);
3829a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3830a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
38319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sEventsTable = "Events";
38329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sAttendeesTable = "Attendees";
38339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sRemindersTable = "Reminders";
38349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sCalendarAlertsTable = "CalendarAlerts";
38359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sExtendedPropertiesTable = "ExtendedProperties";
38369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
38389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
38399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
38409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int DELETED_EVENTS = 4;
38419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS = 5;
38429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_ID = 6;
38439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES = 7;
38449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES_ID = 8;
38459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS = 9;
38469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS_ID = 10;
38479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES = 11;
38489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES_ID = 12;
38499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS = 13;
38509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_ID = 14;
38519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_BY_INSTANCE = 15;
38526db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int INSTANCES_BY_DAY = 16;
38536db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE = 17;
38546db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE_ID = 18;
38556db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES = 19;
38566db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES_ID = 20;
38576db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_DAYS = 21;
385883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM = 22;
385983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff    private static final int SCHEDULE_ALARM_REMOVE = 23;
386048587d3291c4db7f0942e1bff55b88cfa7764ba0Erik    private static final int TIME = 24;
386143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio    private static final int CALENDAR_ENTITIES = 25;
386243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio    private static final int CALENDAR_ENTITIES_ID = 26;
386381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final int INSTANCES_SEARCH = 27;
386481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final int INSTANCES_SEARCH_BY_DAY = 28;
3865315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final int PROVIDER_PROPERTIES = 29;
38669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
38689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
38699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sEventsProjectionMap;
387019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
38719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
38729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
38739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
3874315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
38759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
3877b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/when/*/*", INSTANCES);
3878b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
387981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
388081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/searchbyday/*/*/*",
388181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
3882b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
3883b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events", EVENTS);
3884b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "events/#", EVENTS_ID);
3885b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities", EVENT_ENTITIES);
3886b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
3887b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars", CALENDARS);
3888b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendars/#", CALENDARS_ID);
388943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
389043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
3891b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "deleted_events", DELETED_EVENTS);
3892b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees", ATTENDEES);
3893b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "attendees/#", ATTENDEES_ID);
3894b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders", REMINDERS);
3895b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "reminders/#", REMINDERS_ID);
3896b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
3897b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "extendedproperties/#", EXTENDED_PROPERTIES_ID);
3898b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
3899b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
3900b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "calendar_alerts/by_instance",
3901b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
3902c4e53191b570e09959c5723f4d253977ba48f2d0Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate", SYNCSTATE);
390383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
390483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_PATH, SCHEDULE_ALARM);
390583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff        sUriMatcher.addURI(Calendar.AUTHORITY, SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
390648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik        sUriMatcher.addURI(Calendar.AUTHORITY, "time/#", TIME);
3907997e2e5cb006682bc1a82441304994b458d9745dErik        sUriMatcher.addURI(Calendar.AUTHORITY, "time", TIME);
3908315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sUriMatcher.addURI(Calendar.AUTHORITY, "properties", PROVIDER_PROPERTIES);
39099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
39119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
39129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HTML_URI, "htmlUri");
39139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TITLE, "title");
39149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
39159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DESCRIPTION, "description");
39169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.STATUS, "eventStatus");
39179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
39189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
39199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTSTART, "dtstart");
39209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTEND, "dtend");
39219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
39229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DURATION, "duration");
39239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ALL_DAY, "allDay");
39249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.VISIBILITY, "visibility");
39259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TRANSPARENCY, "transparency");
39269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
39279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
39289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RRULE, "rrule");
39299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RDATE, "rdate");
39309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXRULE, "exrule");
39319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXDATE, "exdate");
39329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
39339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
39349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
39359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.LAST_DATE, "lastDate");
39369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
39379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
39389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
39399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
39409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
39419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORGANIZER, "organizer");
39421b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventsProjectionMap.put(Events.DELETED, "deleted");
39439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3944e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
39451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
39461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
39471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
39489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
3949982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.COLOR, "color");
3950982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.ACCESS_LEVEL, "access_level");
3951982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.SELECTED, "selected");
39521b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventsProjectionMap.put(Calendars.SYNC1, "sync1");
39539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.TIMEZONE, "timezone");
39549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, "ownerAccount");
39559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3956982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
3957e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
3958e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
3959982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3960e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3961982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
39621ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._ID, "_id");
39631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ID, "_sync_id");
39641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_VERSION, "_sync_version");
39651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_TIME, "_sync_time");
3966c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventsProjectionMap.put(Events._SYNC_DATA, "_sync_local_id");
39671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_DIRTY, "_sync_dirty");
39681ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "_sync_account");
39699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE,
39701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                "_sync_account_type");
39719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
397246f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
397319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HTML_URI, "htmlUri");
397419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TITLE, "title");
397519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, "description");
397619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
397719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.STATUS, "eventStatus");
397819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
397919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
398019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTSTART, "dtstart");
398119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTEND, "dtend");
398219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DURATION, "duration");
398319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
398419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ALL_DAY, "allDay");
398519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.VISIBILITY, "visibility");
398619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TRANSPARENCY, "transparency");
398719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
398819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
398919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RRULE, "rrule");
399019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RDATE, "rdate");
399119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXRULE, "exrule");
399219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXDATE, "exdate");
399319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
399419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
399519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
399619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.LAST_DATE, "lastDate");
399719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
399819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
399919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
400019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
400119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
400219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORGANIZER, "organizer");
40031b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventEntitiesProjectionMap.put(Events.DELETED, "deleted");
400419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
400519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
4006c12fe4704e12519756b8da1a3f9199f2013e48f0Marc Blank        sEventEntitiesProjectionMap.put(Events._SYNC_DATA, Events._SYNC_DATA);
400719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_VERSION, Events._SYNC_VERSION);
400819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_DIRTY, Events._SYNC_DIRTY);
40091b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sEventEntitiesProjectionMap.put(Calendars.SYNC1, Calendars.SYNC1);
401019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
40119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
40121b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
40139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
40149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
40159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
40169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
40179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
40189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
40199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
40209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
40219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
40239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
40249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
40259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
40269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
40279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
40289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
40299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
40309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
40329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
40339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
40349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
40359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
40369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
40389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
40399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
40409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
40419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
40429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
40439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
40449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4045315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4046315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4047315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4048315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4049315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
40509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
40519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
40539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make sure that there are no entries for accounts that no longer
40549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * exist. We are overriding this since we need to delete from the
40559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
40567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
40577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
40589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
40599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
4060ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4061ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
4062ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
4063ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4064ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4065ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
40669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
406746f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>();
406846f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
40699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
40709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
40719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
40729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
40739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
40749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
40769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
40779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (String table : new String[]{"Calendars"}) {
4079ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
40809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
40817cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                Cursor c = mDb.rawQuery("SELECT DISTINCT " +
40827cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            Calendar.SyncColumns._SYNC_ACCOUNT +
40837cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
40847cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            Calendar.SyncColumns._SYNC_ACCOUNT_TYPE +
40857cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
40869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
40879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (c.getString(0) != null && c.getString(1) != null) {
40889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
40899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
40909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
40919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
40929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
40939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
40949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
40959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
40969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
4098f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
4099f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
4100f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
41019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
41027cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                mDb.execSQL("DELETE FROM Calendars" +
41037cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                            " WHERE " +
41047cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                Calendar.SyncColumns._SYNC_ACCOUNT + "= ? AND " +
41057cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                Calendar.SyncColumns._SYNC_ACCOUNT_TYPE + "= ?", params);
41069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
41079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
41089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
41099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
41109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
41119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
41123ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
41133ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
4114dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
41159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4117636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
4118636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
4119636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
4120636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
4121636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
4122636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
4123636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
4124636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
4125636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
4126636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
4127636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
4128636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
4129636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
4130636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
4131636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
4132636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
4133636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
4134636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
4135636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
41369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
4137