CalendarProvider2.java revision 8335a18ac6024f302b50e6f473ad4058cc355c85
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
209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.AlarmManager;
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.app.PendingIntent;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Debug;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.DateException;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar;
459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Attendees;
469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.CalendarAlerts;
479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Calendars;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Events;
499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Instances;
509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.Calendar.Reminders;
519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.format.Time;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Config;
549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport com.android.internal.content.SyncStateContentProviderHelper;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport com.google.android.collect.Maps;
589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport com.google.android.collect.Sets;
599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport com.google.wireless.gdata.calendar.client.CalendarClient;
609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Map;
659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.Set;
669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * is defined in {@link android.provider.Calendar}.
719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String TAG = "CalendarProvider2";
759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7656120dd505f0b89776ae71e93ed4e208e1f15e1dKen Shirriff    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean PROFILE = false;
799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3;
937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
1029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
1079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
1099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final Uri SYNCSTATE_CONTENT_URI =
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Uri.parse("content://syncstate/state");
1129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // To determine if a recurrence exception originally overlapped the
1149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // window, we need to assume a maximum duration, since we only know
1159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // the original start time.
1169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int MAX_ASSUMED_DURATION = 7*24*60*60*1000;
1179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class TimeRange {
1199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public boolean allDay;
1229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesRange {
1259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long begin;
1269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public long end;
1279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public InstancesRange(long begin, long end) {
1299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.begin = begin;
1309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            this.end = end;
1319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
1329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class InstancesList
1359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends ArrayList<ContentValues> {
1369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static final class EventInstancesMap
1399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            extends HashMap<String, InstancesList> {
1409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void add(String syncId, ContentValues values) {
1419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            InstancesList instances = get(syncId);
1429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (instances == null) {
1439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                instances = new InstancesList();
1449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                put(syncId, instances);
1459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instances.add(values);
1479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
1489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A thread that runs in the background and schedules the next
1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // calendar event alarm.
1529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class AlarmScheduler extends Thread {
1539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean mRemoveAlarms;
1549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public AlarmScheduler(boolean removeAlarms) {
1569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mRemoveAlarms = removeAlarms;
1579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
1589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
1609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
1619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                runScheduleNextAlarm(mRemoveAlarms);
1639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (SQLException e) {
1649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.e(TAG, "runScheduleNextAlarm() failed", e);
1659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
1679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
1689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
1709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We search backward in time for event reminders that we may have missed
1719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and schedule them if the event has not yet expired.  The amount in
1729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the past to search backwards is controlled by this constant.  It
1739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should be at least a few minutes to allow for an event that was
1749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * recently created on the web to make its way to the phone.  Two hours
1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * might seem like overkill, but it is useful in the case where the user
1769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * just crossed into a new timezone and might have just missed an alarm.
1779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long SCHEDULE_ALARM_SLACK =
1799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        2 * android.text.format.DateUtils.HOUR_IN_MILLIS;
1809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
1829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Alarms older than this threshold will be deleted from the CalendarAlerts
1839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  This should be at least a day because if the timezone is
1849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * wrong and the user corrects it we might delete good alarms that
1859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * appear to be old because the device time was incorrectly in the future.
1869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This threshold must also be larger than SCHEDULE_ALARM_SLACK.  We add
1879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the SCHEDULE_ALARM_SLACK to ensure this.
1889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
1899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * To make it easier to find and debug problems with missed reminders,
1909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * set this to something greater than a day.
1919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long CLEAR_OLD_ALARM_THRESHOLD =
1939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            7 * android.text.format.DateUtils.DAY_IN_MILLIS + SCHEDULE_ALARM_SLACK;
1949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // A lock for synchronizing access to fields that are shared
1969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // with the AlarmScheduler thread.
1979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Object mAlarmLock = new Object();
1989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Allocate the string constant once here instead of on the heap
2089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String CALENDAR_ID_SELECTION = "calendar_id=?";
2099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sInstancesProjection =
2119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            new String[] { Instances.START_DAY, Instances.END_DAY,
2129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Instances.START_MINUTE, Instances.END_MINUTE, Instances.ALL_DAY };
2139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarClient mCalendarClient = null;
2219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager mAlarmManager;
2239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarAppWidgetProvider mAppWidgetProvider = CalendarAppWidgetProvider.getInstance();
2259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
2289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
2309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
2319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
2329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
2339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
2349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
2359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
2379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
2389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
2409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
2419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
2429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
2449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
2459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
2469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
2489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void verifyAccounts() {
2509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
2519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
2629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
2639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
2649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        verifyAccounts();
2669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
2689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
2699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
2719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
2729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
2739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Context c = getContext();
2749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
2769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
2779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
2789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
2799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        c.registerReceiver(mIntentReceiver, filter);
2809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mMetaData = new MetaData(mDbHelper);
2829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        updateTimezoneDependentFields();
2839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
2859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
2899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
2909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * has changes.
2919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
2969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
2989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
2999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
3009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
3019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                doUpdateTimezoneDependentFields();
3039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (SQLException e) {
3049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
3059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
3069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Clear at least the in-memory data (and if possible the
3079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // database fields) to force a re-computation of Instances.
3089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    mMetaData.clearInstanceRange();
3099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (SQLException e2) {
3109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
3119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
3139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
3179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
3189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
3199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void doUpdateTimezoneDependentFields() {
3219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFields();
3229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String localTimezone = TimeZone.getDefault().getID();
3239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (TextUtils.equals(fields.timezone, localTimezone)) {
3249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Even if the timezone hasn't changed, check for missed alarms.
3259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This code executes when the CalendarProvider2 is created and
3269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // helps to catch missed alarms when the Calendar process is
3279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // killed (because of low-memory conditions) and then restarted.
3289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rescheduleMissedAlarms();
3299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
3309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
3339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
3349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
3359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
3369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time();
3379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
3389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
3399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
3409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
3419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
3429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
3439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
3449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
3459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        handleInstanceQuery(qb, begin, end, new String[] { Instances._ID },
3469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */, null /* sort */, false /* searchByDayInsteadOfMillis */);
3479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rescheduleMissedAlarms();
3499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void rescheduleMissedAlarms() {
3529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
3539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager != null) {
3549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Context context = getContext();
3559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentResolver cr = context.getContentResolver();
3569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            CalendarAlerts.rescheduleMissedAlarms(cr, context, manager);
3579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
3619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Appends comma separated ids.
3629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param ids Should not be empty
3639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void appendIds(StringBuilder sb, HashSet<Long> ids) {
3659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (long id : ids) {
3669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            sb.append(id).append(',');
3679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sb.setLength(sb.length() - 1); // Yank the last comma
3709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
3739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void notifyChange() {
3749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
3759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
3769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        getContext().getContentResolver().notifyChange(Calendar.CONTENT_URI, null,
3779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                true /* syncToNetwork */);
3789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
3799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
3819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
3829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
3839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (VERBOSE_LOGGING) {
3849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "query: " + uri);
3859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
3889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
3909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
3919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
3929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
3949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
3959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
3969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
3979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
3989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
4001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
4019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
402595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
4039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
4051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
4069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
407636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
408636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
4099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
41019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
41119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
41219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
41319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
414595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
41519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
41619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
41719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
41819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
419636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
420636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
42119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
42219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
425595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                appendAccountFromParameter(qb, uri);
4269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
4289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Calendars");
429636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
430636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("_id=?");
4319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
4339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
4349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
4359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
4369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
4379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
4389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
4399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
4409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
4419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
4439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
4449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
4459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
4469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
4479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return handleInstanceQuery(qb, begin, end, projection,
4499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        selection, sortOrder, match == INSTANCES_BY_DAY);
4506db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
4519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
4529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
4539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
4549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
4559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
4569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
4579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
4589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4656db535b458146a279bebd4a51d56c1bdfc204528Erik                return handleEventDayQuery(qb, startDay, endDay, projection, selection);
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
4671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
4691ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.appendWhere("Events._id=Attendees.event_id");
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
4721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Attendees, Events");
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
474636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
475636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Attendees._id=?  AND Events._id=Attendees.event_id");
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("Reminders");
4799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
4811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("Reminders, Events");
4829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
483636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
484636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("Reminders._id=? AND Events._id=Reminders.event_id");
4859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
4871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("CalendarAlerts, Events");
4889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
4891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.appendWhere("Events._id=CalendarAlerts.event_id");
4909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
4921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("CalendarAlerts, Events");
4939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
4941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.appendWhere("Events._id=CalendarAlerts.event_id");
4959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
4969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
4979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
4981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables("CalendarAlerts, Events");
4999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
500636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
501636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("CalendarAlerts._id=?  AND Events._id=CalendarAlerts.event_id");
5029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
5039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
5049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setTables("ExtendedProperties");
5059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
5069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
5077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                qb.setTables("ExtendedProperties");
508636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
509636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                qb.appendWhere("ExtendedProperties._id=?");
5109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
5119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
5129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
5139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
5169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
5179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
5209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
5219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
5229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
5239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
5249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
5259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
5269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.setNotificationUri(getContext().getContentResolver(), Calendar.Events.CONTENT_URI);
5279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
5299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
5329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
5339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
5369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
5379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
5389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
5399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
5419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
5429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
5439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
5459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rangeEnd, String[] projection,
5469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String sort, boolean searchByDay) {
5479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setTables("Instances INNER JOIN Events ON (Instances.event_id=Events._id) " +
5499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                "INNER JOIN Calendars ON (Events.calendar_id = Calendars._id)");
5509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
5519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
5529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
5539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
5549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
5559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
5569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
5579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
5589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
5599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
5609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */);
5618335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff            qb.appendWhere("startDay<=? AND endDay>=?");
5629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
5639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
5649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */);
5658335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff            qb.appendWhere("begin<=? AND end>=?");
5669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5678335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(rangeEnd),
5688335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
5698335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
5707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
5719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5736db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
5746db535b458146a279bebd4a51d56c1bdfc204528Erik            String[] projection, String selection) {
5756db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setTables("Instances INNER JOIN Events ON (Instances.event_id=Events._id) " +
5766db535b458146a279bebd4a51d56c1bdfc204528Erik                "INNER JOIN Calendars ON (Events.calendar_id = Calendars._id)");
5776db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
57843556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
57943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
58043556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        Time time = new Time();
58143556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        long beginMs = time.setJulianDay((int) begin);
58243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
58343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
58443556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        long endMs = time.setJulianDay((int) end + 1);
58543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
58643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        acquireInstanceRange(beginMs, endMs, true);
5878335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        qb.appendWhere("startDay<=? AND endDay>=?");
5888335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
5898335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
5908335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
5916db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
5929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
5969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  Acquires the database lock and calls {@link #acquireInstanceRangeLocked}.
5979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
5989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
5999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
6009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
6019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
6029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void acquireInstanceRange(final long begin,
6039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            final long end,
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            final boolean useMinimumExpansionWindow) {
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
6069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
6079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow);
6089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
6099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
6109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
6119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
6159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
6169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
6179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
6189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
6199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
6209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
6219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
6229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void acquireInstanceRangeLocked(long begin, long end,
6239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean useMinimumExpansionWindow) {
6249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
6259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
6269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
6289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
6299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
6309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
6319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
6329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
6339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
6349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
6359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
6369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
6379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
6409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
6419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
6429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
6439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String dbTimezone = fields.timezone;
6449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
6459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
6469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String localTimezone = TimeZone.getDefault().getID();
6479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean timezoneChanged = (dbTimezone == null) || !dbTimezone.equals(localTimezone);
6489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (maxInstance == 0 || timezoneChanged) {
6509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
6519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.execSQL("DELETE FROM Instances;");
6529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Config.LOGV) {
6536db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
6549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
6559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
6569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            expandInstanceRangeLocked(expandBegin, expandEnd, localTimezone);
6579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
658dbd797d59294d72d7ea9226d10128674b634aaadErik            mMetaData.writeLocked(localTimezone, expandBegin, expandEnd);
6599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
6609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
6639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
6649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
6659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
6669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
6679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
6689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
6699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
6709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
6719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
6729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
6739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
6749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Config.LOGV) {
6759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
6769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
6779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
6789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
6799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
6829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
6839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
6849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            expandInstanceRangeLocked(expandBegin, minInstance, localTimezone);
6859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
6869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
6899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
6909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
6919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            expandInstanceRangeLocked(maxInstance, expandEnd, localTimezone);
6929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
6939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
696dbd797d59294d72d7ea9226d10128674b634aaadErik        mMetaData.writeLocked(localTimezone, minInstance, maxInstance);
6979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EXPAND_COLUMNS = new String[] {
7009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._ID,
7019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
7029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.STATUS,
7039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTSTART,
7049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DTEND,
7059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EVENT_TIMEZONE,
7069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
7079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
7089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXRULE,
7099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.EXDATE,
7109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.DURATION,
7119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ALL_DAY,
7129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_EVENT,
7139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.ORIGINAL_INSTANCE_TIME
7149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
7159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
7179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make instances for the given range.
7189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
7199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void expandInstanceRangeLocked(long begin, long end, String localTimezone) {
7209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
7229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.startMethodTracing("expandInstanceRangeLocked");
7239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
7269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Expanding events between " + begin + " and " + end);
7279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getEntries(begin, end);
7309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
7319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            performInstanceExpansion(begin, end, localTimezone, entries);
7329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
7339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
7349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
7359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
7369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (PROFILE) {
7389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Debug.stopMethodTracing();
7399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
7439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Get all entries affecting the given window.
7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
7469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return Cursor for the entries; caller must close it.
7479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
7489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getEntries(long begin, long end) {
7499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
7501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String beginString = String.valueOf(begin);
7549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String endString = String.valueOf(end);
7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // grab recurrence exceptions that fall outside our expansion window but modify
7579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences that do fall within our window.  we won't insert these into the output
7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // set of instances, but instead will just add them to our cancellations list, so we
7599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // can cancel the correct recurrence expansion instances.
7609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we don't have originalInstanceDuration or end time.  for now, assume the original
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // instance lasts no longer than 1 week.
7629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: compute the originalInstanceEndTime or get this from the server.
7638335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        qb.appendWhere("(dtstart <= ? AND (lastDate IS NULL OR lastDate >= ?)) OR " +
7648335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                "(originalInstanceTime IS NOT NULL AND originalInstanceTime <= ? AND " +
7658335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                "originalInstanceTime >= ?)");
7668335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {endString, beginString, endString,
7678335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(begin - MAX_ASSUMED_DURATION)};
7689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
7699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Retrieving events to expand: " + qb.toString());
7709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */,
7738335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                selectionArgs, null /* groupBy */,
7747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
7759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Perform instance expansion on the given entries.
7799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin Window start (ms).
7809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end Window end (ms).
7819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param localTimezone
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param entries The entries to process.
7839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
7849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void performInstanceExpansion(long begin, long end, String localTimezone,
7859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                          Cursor entries) {
7869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        RecurrenceProcessor rp = new RecurrenceProcessor();
7879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int statusColumn = entries.getColumnIndex(Events.STATUS);
7899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtstartColumn = entries.getColumnIndex(Events.DTSTART);
7909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int dtendColumn = entries.getColumnIndex(Events.DTEND);
7919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int eventTimezoneColumn = entries.getColumnIndex(Events.EVENT_TIMEZONE);
7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int durationColumn = entries.getColumnIndex(Events.DURATION);
7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rruleColumn = entries.getColumnIndex(Events.RRULE);
7949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int rdateColumn = entries.getColumnIndex(Events.RDATE);
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exruleColumn = entries.getColumnIndex(Events.EXRULE);
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int exdateColumn = entries.getColumnIndex(Events.EXDATE);
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int allDayColumn = entries.getColumnIndex(Events.ALL_DAY);
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int idColumn = entries.getColumnIndex(Events._ID);
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int syncIdColumn = entries.getColumnIndex(Events._SYNC_ID);
8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalEventColumn = entries.getColumnIndex(Events.ORIGINAL_EVENT);
8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int originalInstanceTimeColumn = entries.getColumnIndex(Events.ORIGINAL_INSTANCE_TIME);
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues initialValues;
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        EventInstancesMap instancesMap = new EventInstancesMap();
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Duration duration = new Duration();
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time eventTime = new Time();
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: entries contains all events that affect the current
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // window.  It consists of:
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  These will be
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed.
8139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Recurrences that included the window.  These will be displayed
8149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    if not canceled.
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Recurrence exceptions that fall in the window.  These will be
8169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    displayed if not cancellations.
8179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
8189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    window (subject to 1 week assumption above), but are outside
8199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    the window.  These will not be displayed.  Cases c and d are
8209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //    distingushed by the start / end time.
8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        while (entries.moveToNext()) {
8239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                initialValues = null;
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                boolean allDay = entries.getInt(allDayColumn) != 0;
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String eventTimezone = entries.getString(eventTimezoneColumn);
8299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (allDay || TextUtils.isEmpty(eventTimezone)) {
8309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // in the events table, allDay events start at midnight.
8319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this forces them to stay at midnight for all day events
8329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: check that this actually does the right thing.
8339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTimezone = Time.TIMEZONE_UTC;
8349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long dtstartMillis = entries.getLong(dtstartColumn);
8379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Long eventId = Long.valueOf(entries.getLong(idColumn));
8389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String durationStr = entries.getString(durationColumn);
8409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (durationStr != null) {
8419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    try {
8429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.parse(durationStr);
8439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    catch (DateException e) {
8459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.w(TAG, "error parsing duration for event "
8469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                + eventId + "'" + durationStr + "'", e);
8479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.sign = 1;
8489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.weeks = 0;
8499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.days = 0;
8509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.hours = 0;
8519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.minutes = 0;
8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        duration.seconds = 0;
8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        durationStr = "+P0S";
8549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
8559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String syncId = entries.getString(syncIdColumn);
8589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String originalEvent = entries.getString(originalEventColumn);
8599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalInstanceTimeMillis = -1;
8619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!entries.isNull(originalInstanceTimeColumn)) {
8629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTimeMillis= entries.getLong(originalInstanceTimeColumn);
8639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int status = entries.getInt(statusColumn);
8659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rruleStr = entries.getString(rruleColumn);
8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String rdateStr = entries.getString(rdateColumn);
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exruleStr = entries.getString(exruleColumn);
8699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String exdateStr = entries.getString(exdateColumn);
8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceSet recur = new RecurrenceSet(rruleStr, rdateStr, exruleStr, exdateStr);
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (recur.hasRecurrence()) {
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is repeating
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (status == Events.STATUS_CANCELED) {
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen!
8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.e(TAG, "Found canceled recurring event in "
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                + "Events table.  Ignoring.");
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        continue;
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // need to parse the event into a local calendar.
8849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.timezone = eventTimezone;
8859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.set(dtstartMillis);
8869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    eventTime.allDay = allDay;
8879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // should not happen.
8909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.e(TAG, "Repeating event has no duration -- "
8919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                + "should not happen.");
8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (allDay) {
8939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // set to one day.
8949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
8969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 1;
8979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
8989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
8999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.seconds = 0;
9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            durationStr = "+P1D";
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
9029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // compute the duration from dtend, if we can.
9039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            // otherwise, use 0s.
9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.sign = 1;
9059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.weeks = 0;
9069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.days = 0;
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.hours = 0;
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            duration.minutes = 0;
9099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            if (!entries.isNull(dtendColumn)) {
9109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                long dtendMillis = entries.getLong(dtendColumn);
9119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = (int) ((dtendMillis - dtstartMillis) / 1000);
9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P" + duration.seconds + "S";
9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            } else {
9149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                duration.seconds = 0;
9159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                durationStr = "+P0S";
9169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            }
9179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
9189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long[] dates;
9219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    dates = rp.expand(eventTime, recur, begin, end);
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // Initialize the "eventTime" timezone outside the loop.
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This is used in computeTimezoneDependentFields().
9259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long durationMillis = duration.getMillis();
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    for (long date : dates) {
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues = new ContentValues();
9349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.EVENT_ID, eventId);
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.BEGIN, date);
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        long dtendMillis = date + durationMillis;
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Instances.END, dtendMillis);
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        computeTimezoneDependentFields(date, dtendMillis,
9419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                eventTime, initialValues);
9429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        instancesMap.add(syncId, initialValues);
9439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else {
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // the event is not repeating
9469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues = new ContentValues();
9479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // if this event has an "original" field, then record
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // that we need to cancel the original event (we can't
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // do that here because the order of this loop isn't
9519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // defined)
9529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (originalEvent != null && originalInstanceTimeMillis != -1) {
9539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.ORIGINAL_EVENT, originalEvent);
9549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.ORIGINAL_INSTANCE_TIME,
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                originalInstanceTimeMillis);
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        initialValues.put(Events.STATUS, status);
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long dtendMillis = dtstartMillis;
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (durationStr == null) {
9619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!entries.isNull(dtendColumn)) {
9629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            dtendMillis = entries.getLong(dtendColumn);
9639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        dtendMillis = duration.addTo(dtstartMillis);
9669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // this non-recurring event might be a recurrence exception that doesn't
9699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // actually fall within our expansion window, but instead was selected
9709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so we can correctly cancel expanded recurrence instances below.  do not
9719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // add events to the instances map if they don't actually fall within our
9729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // expansion window.
9739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if ((dtendMillis < begin) || (dtstartMillis > end)) {
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (originalEvent != null && originalInstanceTimeMillis != -1) {
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            initialValues.put(Events.STATUS, Events.STATUS_CANCELED);
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        } else {
9779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Log.w(TAG, "Unexpected event outside window: " + syncId);
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            continue;
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
9809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.EVENT_ID, eventId);
9839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.BEGIN, dtstartMillis);
9849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    initialValues.put(Instances.END, dtendMillis);
9869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (allDay) {
9889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = Time.TIMEZONE_UTC;
9899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    } else {
9909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        eventTime.timezone = localTimezone;
9919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
9929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    computeTimezoneDependentFields(dtstartMillis, dtendMillis,
9939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            eventTime, initialValues);
9949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    instancesMap.add(syncId, initialValues);
9969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (DateException e) {
9989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.w(TAG, "RecurrenceProcessor error ", e);
9999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } catch (TimeFormatException e) {
10009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.w(TAG, "RecurrenceProcessor error ", e);
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
10029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains all instances that affect the
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // window, indexed by original sync id.  It consists of:
10069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.  They have:
10079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
10089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window.  They may
10099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be subject to exceptions.  They have:
10109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   EVENT_ID, BEGIN, END
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They have:
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS (since they can
10139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   be a modification or cancellation), EVENT_ID, BEGIN, END
10149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
10159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  They have:
10169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   ORIGINAL_EVENT, ORIGINAL_INSTANCE_TIME, STATUS =
10179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED, EVENT_ID, BEGIN, END
10189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // First, delete the original instances corresponding to recurrence
10209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // exceptions.  We do this by iterating over the list and for each
10219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrence exception, we search the list for an instance with a
10229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // matching "original instance time".  If we find such an instance,
10239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we remove it from the list.  If we don't find such an instance
10249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then we cancel the recurrence exception.
10259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Set<String> keys = instancesMap.keySet();
10269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (String syncId : keys) {
10279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            InstancesList list = instancesMap.get(syncId);
10289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
10299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // If this instance is not a recurrence exception, then
10319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // skip it.
10329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.ORIGINAL_EVENT)) {
10339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
10349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
10359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
10379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long originalTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                InstancesList originalList = instancesMap.get(originalEvent);
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (originalList == null) {
10409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // The original recurrence is not present, so don't try canceling it.
10419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
10429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
10439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Search the original event for a matching original
10459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance time.  If there is a matching one, then remove
10469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the original one.  We do this both for exceptions that
10479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // change the original instance as well as for exceptions
10489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // that delete the original instance.
10499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                for (int num = originalList.size() - 1; num >= 0; num--) {
10509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    ContentValues originalValues = originalList.get(num);
10519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long beginTime = originalValues.getAsLong(Instances.BEGIN);
10529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (beginTime == originalTime) {
10539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // We found the original instance, so remove it.
10549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        originalList.remove(num);
10559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
10569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
10579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
10589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Invariant: instancesMap contains filtered instances.
10619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // It consists of:
10629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // a) Individual events that fall in the window.
10639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // b) Instances of recurrences that fall in the window and have not
10649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   been subject to exceptions.
10659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // c) Exceptions that fall in the window.  They will have
10669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   STATUS_CANCELED if they are cancellations.
10679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // d) Recurrence exceptions that modify an instance inside the
10689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //   window but fall outside the window.  These are STATUS_CANCELED.
10699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do the inserts.  Since the db lock is held when this method is executed,
10719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // this will be done in a transaction.
10729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if there is lock contention (e.g., a sync is trying to merge into the db
10739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // while the calendar app is trying to query the db (expanding instances)), we will
10749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // not be "polite" and yield the lock until we're done.  This will favor local query
10759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // operations over sync/write operations.
10769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (String syncId : keys) {
10779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            InstancesList list = instancesMap.get(syncId);
10789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (ContentValues values : list) {
10799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // If this instance was cancelled then don't create a new
10819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // instance.
10829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer status = values.getAsInteger(Events.STATUS);
10839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (status != null && status == Events.STATUS_CANCELED) {
10849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
10859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
10869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Remove these fields before inserting a new instance
10889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.ORIGINAL_EVENT);
10899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
10909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.remove(Events.STATUS);
10919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mDbHelper.instancesInsert(values);
10939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
10949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
10989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Computes the timezone-dependent fields of an instance of an event and
10999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * updates the "values" map to contain those fields.
11009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
11019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin the start time of the instance (in UTC milliseconds)
11029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end the end time of the instance (in UTC milliseconds)
11039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param local a Time object with the timezone set to the local timezone
11049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values a map that will contain the timezone-dependent fields
11059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
11069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void computeTimezoneDependentFields(long begin, long end,
11079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local, ContentValues values) {
11089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(begin);
11099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startDay = Time.getJulianDay(begin, local.gmtoff);
11109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int startMinute = local.hour * 60 + local.minute;
11119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        local.set(end);
11139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endDay = Time.getJulianDay(end, local.gmtoff);
11149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int endMinute = local.hour * 60 + local.minute;
11159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Special case for midnight, which has endMinute == 0.  Change
11179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that to +24 hours on the previous day to make everything simpler.
11189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Exception: if start and end minute are both 0 on the same day,
11199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // then leave endMinute alone.
11209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (endMinute == 0 && endDay > startDay) {
11219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endMinute = 24 * 60;
11229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            endDay -= 1;
11239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
11249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_DAY, startDay);
11269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_DAY, endDay);
11279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.START_MINUTE, startMinute);
11289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Instances.END_MINUTE, endMinute);
11299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
11329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
11339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
11349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
11359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
11369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
11379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
11389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
11399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
11409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
11419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
11429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
11439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
11449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
11459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
11469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
11479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
11489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
11499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
11509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
11516db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
11529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
11539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
11549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
11559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
11569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public static boolean isRecurrenceEvent(ContentValues values) {
11599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return (!TextUtils.isEmpty(values.getAsString(Events.RRULE))||
11609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.RDATE))||
11619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_EVENT)));
11629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
11659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected Uri insertInTransaction(Uri uri, ContentValues values) {
11669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (VERBOSE_LOGGING) {
11679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
11689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
11699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
11719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
11729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
11749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
11759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
11779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff              case SYNCSTATE:
11789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
11799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
11809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
11817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
11827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
11837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
11849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
11859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
11869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
11879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: avoid the call to updateBundleFromEvent if this is just finding local
11887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                // changes.
11899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
11909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                ContentValues updatedValues = updateContentValuesFromEvent(values);
11919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
11929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
11939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
11949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
11959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
11969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
11979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
11989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
11999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
12009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
12019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
12029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
12039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
12049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
12059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
12069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
12099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
12109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
12119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, true /* new event */, mDb);
12129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
12149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
12159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
12169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
12179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
12189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
12199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
12209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
12219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
12229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    triggerAppWidgetUpdate(id);
12239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
12269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
12279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
12289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountName = values.getAsString(Calendars._SYNC_ACCOUNT);
12299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
12309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Calendars._SYNC_ACCOUNT_TYPE);
12319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
12329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String calendarUrl = values.getAsString(Calendars.URL);
12339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    mDbHelper.scheduleSync(account, false /* two-way sync */, calendarUrl);
12349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
12369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
12399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
12419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.attendeesInsert(values);
12437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
12447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Attendees.EVENT_ID));
12457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
12469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
12489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
12499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
12519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
12529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
12549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.remindersInsert(values);
12567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
12577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Reminders.EVENT_ID));
12587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
12599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
12619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
12629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
12639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
12659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
12679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
12689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
12722fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
12732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Calendar.ExtendedProperties.EVENT_ID)) {
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
12799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.extendedPropertiesInsert(values);
12817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
12827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    setEventDirty(values.getAsInteger(Calendar.ExtendedProperties.EVENT_ID));
12837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
12849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
12859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
12869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
12879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
12889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
12899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
12909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
12919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
12926db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
12937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
12969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private void setEventDirty(int eventId) {
1306636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=?", new Integer[] {eventId});
13077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
13087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
13099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
13119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
13129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
13139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
13169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
13179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
13199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
13409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
13689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
13699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
13709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
13719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
13729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
13739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
13759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
13769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
13789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "Couldn't find " + eventId + " in Events table");
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
13979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
13989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
13999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
14009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
14039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
14099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
14279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
14289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
1447636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        db.update("Events", values, "_id=?", new String[] {String.valueOf(eventId)});
14489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
14519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the instances table when an event is added or updated.
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param newEvent true if the event is new.
14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
14569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateInstancesLocked(ContentValues values,
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean newEvent,
14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
14619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If there are no expanded Instances, then return.
14639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
14649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (fields.maxInstance == 0) {
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis == null) {
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (newEvent) {
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // must be present for a new event.
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART missing.");
14739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Config.LOGV) Log.v(TAG, "Missing DTSTART.  "
14759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + "No need to update instance.");
14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
14819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!newEvent) {
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Want to do this for regular event, recurrence, or exception.
14849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // For recurrence or exception, more deletion may happen below if we
14859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // do an instance expansion.  This deletion will suffice if the exception
14869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // is moved outside the window, for instance.
1487636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
14889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (isRecurrenceEvent(values))  {
14919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // The recurrence or exception needs to be (re-)expanded if:
14929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // a) Exception or recurrence that falls inside window
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean insideWindow = dtstartMillis <= fields.maxInstance &&
14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    (lastDateMillis == null || lastDateMillis >= fields.minInstance);
14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // b) Exception that affects instance inside window
14969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // These conditions match the query in getEntries
14979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            //  See getEntries comment for explanation of subtracting 1 week.
14989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean affectsWindow = originalInstanceTime != null &&
14999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime <= fields.maxInstance &&
15009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
15019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (insideWindow || affectsWindow) {
15029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateRecurrenceInstancesLocked(values, rowId, db);
15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: an exception creation or update could be optimized by
15059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // updating just the affected instances, instead of regenerating
15069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the recurrence.
15079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
15089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
15119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis == null) {
15129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            dtendMillis = dtstartMillis;
15139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // if the event is in the expanded range, insert
15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // into the instances table.
15179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: deal with durations.  currently, durations are only used in
15189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrences.
15199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
15219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentValues instanceValues = new ContentValues();
15229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.EVENT_ID, rowId);
15239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.BEGIN, dtstartMillis);
15249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            instanceValues.put(Instances.END, dtendMillis);
15259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            boolean allDay = false;
15279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
15289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
15299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                allDay = allDayInteger != 0;
15309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
15319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Update the timezone-dependent fields.
15339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time local = new Time();
15349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDay) {
15359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = Time.TIMEZONE_UTC;
15369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
15379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                local.timezone = fields.timezone;
15389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
15399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
15419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.instancesInsert(instanceValues);
15429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
15469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Determines the recurrence entries associated with a particular recurrence.
15479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This set is the base recurrence and any exception.
15489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
15499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Normally the entries are indicated by the sync id of the base recurrence
15509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * (which is the originalEvent in the exceptions).
15519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * However, a complication is that a recurrence may not yet have a sync id.
15529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * In that case, the recurrence is specified by the rowId.
15539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
15549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param recurrenceSyncId The sync id of the base recurrence, or null.
15559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The row id of the base recurrence.
15569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return the relevant entries.
15579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
15589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor getRelevantRecurrenceEntries(String recurrenceSyncId, long rowId) {
15599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
15609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15611ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
15629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sEventsProjectionMap);
1563636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        String selectionArgs[];
15649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
1565636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_id =?";
15669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
1567636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {String.valueOf(rowId)};
15689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
1569636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String where = "_sync_id = ? OR originalEvent = ?";
15709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            qb.appendWhere(where);
1571636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId};
15729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.VERBOSE)) {
15749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "Retrieving events to expand: " + qb.toString());
15759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1577636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs,
15787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* groupBy */, null /* having */, null /* sortOrder */);
15799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
15819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
15829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Do incremental Instances update of a recurrence or recurrence exception.
15839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
15849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method does performInstanceExpansion on just the modified recurrence,
15859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid the overhead of recomputing the entire instance table.
15869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
15879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param values The new values of the event.
15889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rowId The database row id of the event.
15899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db The database
15909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
15919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateRecurrenceInstancesLocked(ContentValues values,
15929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long rowId,
15939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            SQLiteDatabase db) {
15949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
15959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
15969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String recurrenceSyncId = null;
15979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalEvent != null) {
15989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = originalEvent;
15999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
16009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the recurrence's sync id from the database
16019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            recurrenceSyncId = DatabaseUtils.stringForQuery(db, "SELECT _sync_id FROM Events"
1602636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    + " WHERE _id=?", new String[] {String.valueOf(rowId)});
16039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // recurrenceSyncId is the _sync_id of the underlying recurrence
16059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the recurrence hasn't gone to the server, it will be null.
16069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Need to clear out old instances
16089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (recurrenceSyncId == null) {
16099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating updating a recurrence that hasn't gone to the server.
16109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Need to delete based on row id
16119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
16129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
16139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
16149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._id =?)";
16159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{"" + rowId});
16169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
16179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Creating or modifying a recurrence or exception.
16189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Delete instances for recurrence (_sync_id = recurrenceSyncId)
16199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // and all exceptions (originalEvent = recurrenceSyncId)
16209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String where = "_id IN (SELECT Instances._id as _id"
16219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " FROM Instances INNER JOIN Events"
16229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " ON (Events._id = Instances.event_id)"
16239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " WHERE Events._sync_id =?"
16249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + " OR Events.originalEvent =?)";
16259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.delete("Instances", where, new String[]{recurrenceSyncId, recurrenceSyncId});
16269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Now do instance expansion
16299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor entries = getRelevantRecurrenceEntries(recurrenceSyncId, rowId);
16309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
16319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            performInstanceExpansion(fields.minInstance, fields.maxInstance, fields.timezone,
16329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                     entries);
16339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
16349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (entries != null) {
16359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                entries.close();
16369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
16379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1639dbd797d59294d72d7ea9226d10128674b634aaadErik        // Clear busy bits (is this still needed?)
1640dbd797d59294d72d7ea9226d10128674b634aaadErik        mMetaData.writeLocked(fields.timezone, fields.minInstance, fields.maxInstance);
16419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
16429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
16449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
16459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
16469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
16479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
16489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
16499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
16509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
16519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
16529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
16539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
16549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
16559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
16569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
16579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
16589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
16599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
16609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
16629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
16639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
16649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
16659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
16669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
16679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
16689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
16699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
16709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
16719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
16729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
16739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
16749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
16759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
16769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            RecurrenceSet recur = new RecurrenceSet(values);
16789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (recur.hasRecurrence()) {
16809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
16819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
16829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
16849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
16869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
16879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
16889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
16899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
16909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
16929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
16939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
16949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
16959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
16969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
16979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
16989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
16999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
17009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
17019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
17049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
17059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
17079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
17089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private ContentValues updateContentValuesFromEvent(ContentValues initialValues) {
17109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
17119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            ContentValues values = new ContentValues(initialValues);
17129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
17149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
17159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
17169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
17199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
17209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
17219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.w(TAG, "Could not calculate last date.", e);
17229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
17239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
17259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
17279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
17289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        rawValues.put("event_id", eventId);
17309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
17329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
17349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
17359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
17369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
17379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
17409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
17419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
17429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
17459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
17469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
17479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
17489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
17499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtstart2445", time.format2445());
17509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
17539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
17549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
17559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("dtend2445", time.format2445());
17569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
17599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
17609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
17619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
17629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
17639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
17649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
17659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
17669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
17679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
17689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("originalInstanceTime2445", time.format2445());
17699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
17729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
17739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
17749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
17759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            rawValues.put("lastDate2445", time.format2445());
17769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
17799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
17809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
17829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
17839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (VERBOSE_LOGGING) {
17849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
17859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
17869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
17879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
17889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
17899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
17909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
17919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
17929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
17949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String selectionWithId =
17959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        (BaseColumns._ID + "=" + ContentUris.parseId(uri) + " ")
17969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
17979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId, selectionArgs);
17989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
17991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
18009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
18021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                selection = appendAccountToSelection(uri, selection);
18037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
18041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
18051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                Cursor cursor = mDb.query("Events", ID_ONLY_PROJECTION,
18061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
18077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
18089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
18091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
18101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
18111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        result += deleteEventInternal(id, callerIsSyncAdapter);
18129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
18139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
18149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
18159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
18169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
18189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18191ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
18201ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
18211ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
18221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
18231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
18241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
18251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
18261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
18271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                return deleteEventInternal(id, callerIsSyncAdapter);
18281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
18299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
18309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
18327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Attendees", selection, selectionArgs);
18337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
18347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Attendees", uri, selection, selectionArgs);
18357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
18389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
18402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
18412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
18427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
18437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
1844636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Attendees", "_id=?", new String[] {String.valueOf(id)});
18457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
18462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Attendees", uri, null /* selection */,
18472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
18487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
18519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
18537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("Reminders", selection, selectionArgs);
18547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
18557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("Reminders", uri, selection, selectionArgs);
18567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
18599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
18612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
18622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
18637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
18647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
1865636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("Reminders", "_id=?", new String[] {String.valueOf(id)});
18667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
18672fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("Reminders", uri, null /* selection */,
18682fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
18692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
18702fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
18712fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
18722fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
18732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
18742fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return mDb.delete("ExtendedProperties", selection, selectionArgs);
18752fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
18762fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, selection, selectionArgs);
18772fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
18782fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
18792fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
18802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
18812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
18822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
18832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
18842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
18852fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
1886636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.delete("ExtendedProperties", "_id=?",
1887636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
18882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
18892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return deleteFromTable("ExtendedProperties", uri, null /* selection */,
18902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
18917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
18939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
18949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
18957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
18967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return mDb.delete("CalendarAlerts", selection, selectionArgs);
18977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
18987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    return deleteFromTable("CalendarAlerts", uri, selection, selectionArgs);
18997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
19009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
19019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
19029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
19032fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
19042fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
19052fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
19062fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
19072fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
19089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
1909636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.delete("CalendarAlerts", "_id=?", new String[] {String.valueOf(id)});
19109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
19119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case DELETED_EVENTS:
19127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
19139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
19149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                StringBuilder selectionSb = new StringBuilder("_id=");
19159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
19169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
19179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
19189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
19199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
19209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
19219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
19229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
19239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
1924595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
19257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return deleteMatchingCalendars(selection); // TODO: handle in sync adapter
19269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
19279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
19286db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
19299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
19309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
19319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
19329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
19339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
19349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private int deleteEventInternal(long id, boolean callerIsSyncAdapter) {
19361ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
19371ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
19381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
19391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        Cursor cursor = mDb.query("Events", EVENTS_PROJECTION,
1940636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                "_id=?", new String[] {String.valueOf(id)},
1941636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
19421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
19431ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
19441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
19451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
19461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
19471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (!TextUtils.isEmpty(syncId)) {
19481ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
19491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // TODO: we may also want to delete exception
19501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // events for this event (in case this was a
19511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // recurring event).  We can do that with the
19521ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // following code:
19531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    // mDb.delete("Events", "originalEvent=?", new String[] {syncId});
19541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
19551ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
19561ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
19571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
19581ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
19591ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
19601ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
19611ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX);
19621ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)
19631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        || !TextUtils.isEmpty(origEvent)) {
19641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
19651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
19661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
19671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (callerIsSyncAdapter) {
1968636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    mDb.delete("Events", "_id=?", new String[] {String.valueOf(id)});
19691ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
19701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
19711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events.DELETED, 1);
19721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
1973636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    mDb.update("Events", values, "_id=?", new String[] {String.valueOf(id)});
19741ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
19751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
19761ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
19771ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
19781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
19791ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
19801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        triggerAppWidgetUpdate(-1);
19811ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
1982636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(id)};
1983636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("Instances", "event_id=?", selectionArgs);
1984636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("EventsRawTimes", "event_id=?", selectionArgs);
1985636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("Attendees", "event_id=?", selectionArgs);
1986636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("Reminders", "event_id=?", selectionArgs);
1987636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("CalendarAlerts", "event_id=?", selectionArgs);
1988636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        mDb.delete("ExtendedProperties", "event_id=?", selectionArgs);
19891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
19901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
19911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
19927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
19937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
19947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
19957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
19967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
19977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
19987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
19997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
20007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
20017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
20027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
20037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues values = new ContentValues();
20047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        values.put(Events._SYNC_DIRTY, "1");
20057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
20067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
20077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
20087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
20097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2010636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.delete(table, "_id=?", new String[] {String.valueOf(id)});
2011636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", values, "_id=?", new String[] {String.valueOf(event_id)});
20127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
20137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
20147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
20157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
20167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
20177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
20187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
20197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
20207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
20217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
20227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
20237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
20247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
20257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
20267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
20277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
20287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
20297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
20307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
20317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
20327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
20337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        ContentValues dirtyValues = new ContentValues();
20347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        dirtyValues.put(Events._SYNC_DIRTY, "1");
20357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
20367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
20377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
20387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = c.getLong(ID_INDEX);
20397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long event_id = c.getLong(EVENT_ID_INDEX);
2040636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update(table, values, "_id=?", new String[] {String.valueOf(id)});
2041636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                mDb.update("Events", dirtyValues, "_id=?", new String[] {String.valueOf(event_id)});
20427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
20437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
20447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
20457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
20467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
20477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
20487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
20497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private int deleteMatchingCalendars(String where) {
20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int numDeleted = 0;
20567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        Cursor c = mDb.query("Calendars", sCalendarsIdProjection, where,
20577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* selectionArgs */, null /* groupBy */,
20587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, null /* sortOrder */);
20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
20609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
20619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
20639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
20649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
20659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
20669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.deleteRow();
20679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                numDeleted++;
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return numDeleted;
20739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
20749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: call calculateLastDate()!
20769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String[] selectionArgs) {
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (VERBOSE_LOGGING) {
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
20819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final boolean callerIsSyncAdapter =
20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                readBooleanQueryParameter(uri, Calendar.CALLER_IS_SYNCADAPTER, false);
20899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
20917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS && match != EVENTS) {
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throw new IllegalArgumentException(
20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    "WHERE based updates not supported");
20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
20979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
20989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
20999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String selectionWithId =
21039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        (BaseColumns._ID + "=" + ContentUris.parseId(uri) + " ")
21049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
21059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        selectionWithId, selectionArgs);
21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
21109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
21112fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
21122fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
21132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
21149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
21159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
21179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2120636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Calendars", values, "_id=?",
2121636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
21249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
21269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
21279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
21287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = 0;
21297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (match == EVENTS_ID) {
21307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    id = ContentUris.parseId(uri);
2131a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                } else if (callerIsSyncAdapter) {
2132a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    if (selection != null && selection.startsWith("_id=")) {
21337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // The ContentProviderOperation generates an _id=n string instead of
21347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // adding the id to the URL, so parse that out here.
21357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        id = Long.parseLong(selection.substring(4));
2136a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    } else {
2137a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // Sync adapter Events operation affects just Events table, not associated
2138a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // tables.
2139a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        return mDb.update("Events", values, selection, selectionArgs);
2140a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    }
21417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2142a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    throw new IllegalArgumentException("Unknown URL " + uri);
21437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    values.put(Events._SYNC_DIRTY, 1);
21467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Disallow updating the attendee status in the Events
21489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // table.  In the future, we could support this but we
21499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // would have to query and update the attendees table
21509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // to keep the values consistent.
21519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
21529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
21539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.SELF_ATTENDEE_STATUS
21549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
21559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                // TODO: should we allow this?
21587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (values.containsKey(Events.HTML_URI) && !callerIsSyncAdapter) {
21599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
21609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.HTML_URI
21619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
21629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                ContentValues updatedValues = updateContentValuesFromEvent(values);
21659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
21669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.w(TAG, "Could not update event.");
21679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return 0;
21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2170636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                int result = mDb.update("Events", updatedValues, "_id=?",
2171636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (result > 0) {
21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
21749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb);
21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.DTSTART)) {
21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // The start time of the event changed, so run the
21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        // event alarm scheduler.
21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (Log.isLoggable(TAG, Log.DEBUG)) {
21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            Log.d(TAG, "updateInternal() changing event");
21819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        scheduleNextAlarm(false /* do not remove alarms */);
21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        triggerAppWidgetUpdate(id);
21849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
21859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
21879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
21882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
21892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
21902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
21912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
21929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
21939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
21949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
21967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2197636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("Attendees", values, "_id=?",
2198636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
21997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
22002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("Attendees", values, uri, null /* selection */,
22012fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
22027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22042fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
22052fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
22062fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
22072fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
22082fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
22092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2211636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                return mDb.update("CalendarAlerts", values, "_id=?",
2212636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
22152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
22162fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
22179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDb.update("CalendarAlerts", values, selection, selectionArgs);
22189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
22202fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
22212fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
22222fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
22237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
22247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2225636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    count = mDb.update("Reminders", values, "_id=?",
2226636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
22277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
22282fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    count = updateInTable("Reminders", values, uri, null /* selection */,
22292fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
22307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
22329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
22339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
22349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
22369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                scheduleNextAlarm(false /* do not remove alarms */);
22387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
22399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
22412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
22422fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
22432fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
22447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
22457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2246636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                    return mDb.update("ExtendedProperties", values, "_id=?",
2247636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
22487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
22492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    return updateInTable("ExtendedProperties", values, uri, null /* selection */,
22502fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
22517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2259595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    private void appendAccountFromParameter(SQLiteQueryBuilder qb, Uri uri) {
2260595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME);
2261595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE);
2262595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
2263595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere(Calendar.Calendars._SYNC_ACCOUNT + "="
2264595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
2265595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
2266595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
2267595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
2268595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            qb.appendWhere("1"); // I.e. always true
2269595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
2270595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
2271595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
2273595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountName = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_NAME);
2274595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String accountType = getQueryParameter(uri, Calendar.EventsEntity.ACCOUNT_TYPE);
22759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
22769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            StringBuilder selectionSb = new StringBuilder(Calendar.Calendars._SYNC_ACCOUNT + "="
22779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
22789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "="
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!TextUtils.isEmpty(selection)) {
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(" AND (");
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(selection);
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(')');
22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selectionSb.toString();
22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                new String[] {Calendars._SYNC_ACCOUNT, Calendars._SYNC_ACCOUNT_TYPE,
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Calendars.URL, Calendars.SYNC_EVENTS},
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
22989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (cursor != null && cursor.moveToFirst()) {
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                final String accountName = cursor.getString(0);
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                final String accountType = cursor.getString(1);
23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                account = new Account(accountName, accountType);
23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarUrl = cursor.getString(2);
23109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                oldSyncEvents = (cursor.getInt(3) != 0);
23119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
23139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (account == null || TextUtils.isEmpty(calendarUrl)) {
23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.w(TAG, "Cannot update subscription because account "
23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + "or calendar url empty -- should not happen.");
23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If we are no longer syncing a calendar then make sure that the
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // old calendar sync data is cleared.  Then if we later add this
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // calendar back, we will sync all the events.
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!syncEvents) {
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: clear out the SyncState
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            byte[] data = readSyncDataBytes(account);
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            GDataSyncData syncData = AbstractGDataSyncAdapter.newGDataSyncDataFromBytes(data);
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            if (syncData != null) {
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//                syncData.feedData.remove(calendarUrl);
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//                data = AbstractGDataSyncAdapter.newBytesFromGDataSyncData(syncData);
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//                writeSyncDataBytes(account, data);
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            }
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Delete all of the events in this calendar to save space.
23429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is the closest we can come to deleting a calendar.
23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Clients should never actually delete a calendar.  That won't
23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // work.  We need to keep the calendar entry in the Calendars table
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // in order to know not to sync the events for that calendar from
23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the server.
2347636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] args = new String[] {String.valueOf(id)};
23489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.delete("Events", CALENDAR_ID_SELECTION, args);
23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: cancel any pending/ongoing syncs for this calendar.
23519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: there is a corner case to deal with here: namely, if
23539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // we edit or delete an event on the phone and then remove
23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // (that is, stop syncing) a calendar, and if we also make a
23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // change on the server to that event at about the same time,
23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // then we will never propagate the changes from the phone to
23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // the server.
23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
23629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
23639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: is this needed
23669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    @Override
23679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    public void onSyncStop(SyncContext context, boolean success) {
23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        super.onSyncStop(context, success);
23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        if (Log.isLoggable(TAG, Log.DEBUG)) {
23709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//            Log.d(TAG, "onSyncStop() success: " + success);
23719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        }
23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        scheduleNextAlarm(false /* do not remove alarms */);
23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//        triggerAppWidgetUpdate(-1);
23749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff//    }
23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Update any existing widgets with the changed events.
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param changedEventId Specific event known to be changed, otherwise -1.
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *            If present, we use it to decide if an update is necessary.
23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private synchronized void triggerAppWidgetUpdate(long changedEventId) {
23839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Context context = getContext();
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (context != null) {
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mAppWidgetProvider.providerUpdated(context, changedEventId);
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void bootCompleted() {
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Remove alarms from the CalendarAlerts table that have been marked
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // as "scheduled" but not fired yet.  We do this because the
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // AlarmManagerService loses all information about alarms when the
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // power turns off but we store the information in a database table
23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that persists across reboots. See the documentation for
23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // scheduleNextAlarmLocked() for more information.
23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        scheduleNextAlarm(true /* remove alarms */);
23979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Retrieve and cache the alarm manager */
24009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private AlarmManager getAlarmManager() {
24019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        synchronized(mAlarmLock) {
24029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (mAlarmManager == null) {
24039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Context context = getContext();
24049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (context == null) {
24059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.e(TAG, "getAlarmManager() cannot get Context");
24069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return null;
24079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
24089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Object service = context.getSystemService(Context.ALARM_SERVICE);
24099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mAlarmManager = (AlarmManager) service;
24109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return mAlarmManager;
24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarmCheck(long triggerTime) {
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager manager = getAlarmManager();
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (manager == null) {
24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.e(TAG, "scheduleNextAlarmCheck() cannot get AlarmManager");
24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Context context = getContext();
24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Intent intent = new Intent(CalendarReceiver.SCHEDULE);
24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        intent.setClass(context, CalendarReceiver.class);
24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        PendingIntent pending = PendingIntent.getBroadcast(context,
24259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_NO_CREATE);
24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (pending != null) {
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Cancel any previous alarms that do the same thing.
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            manager.cancel(pending);
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        pending = PendingIntent.getBroadcast(context,
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(triggerTime);
24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String timeStr = time.format(" %a, %b %d, %Y %I:%M%P");
24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "scheduleNextAlarmCheck at: " + triggerTime + timeStr);
24389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        manager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
24419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
24449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs the alarm scheduler in a background thread.
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    void scheduleNextAlarm(boolean removeAlarms) {
24479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new AlarmScheduler(removeAlarms);
24489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread and schedules an alarm for
24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the next calendar event, if necessary.
24549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void runScheduleNextAlarm(boolean removeAlarms) {
24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.beginTransaction();
24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
24599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (removeAlarms) {
24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                removeScheduledAlarmsLocked(db);
24619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            scheduleNextAlarmLocked(db);
24639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.setTransactionSuccessful();
24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
24659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            db.endTransaction();
24669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method looks at the 24-hour window from now for any events that it
24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * needs to schedule.  This method runs within a database transaction. It
24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * also runs in a background thread.
24739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The CalendarProvider2 keeps track of which alarms it has already scheduled
24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * to avoid scheduling them more than once and for debugging problems with
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms.  It stores this knowledge in a database table called CalendarAlerts
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * which persists across reboots.  But the actual alarm list is in memory
24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and disappears if the phone loses power.  To avoid missing an alarm, we
24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * clear the entries in the CalendarAlerts table when we start up the
24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * CalendarProvider2.
24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Scheduling an alarm multiple times is not tragic -- we filter out the
24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * extra ones when we receive them. But we still need to keep track of the
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled alarms. The main reason is that we need to prevent multiple
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * notifications for the same alarm (on the receive side) in case we
24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * accidentally schedule the same alarm multiple times.  We don't have
24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * visibility into the system's alarm list so we can never know for sure if
24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we have already scheduled an alarm and it's better to err on scheduling
24899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * an alarm twice rather than missing an alarm.  Another reason we keep
24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * track of scheduled alarms in a database table is that it makes it easy to
24919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * run an SQL query to find the next reminder that we haven't scheduled.
24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void scheduleNextAlarmLocked(SQLiteDatabase db) {
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AlarmManager alarmManager = getAlarmManager();
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (alarmManager == null) {
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.e(TAG, "Failed to find the AlarmManager. Could not schedule the next alarm!");
24999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
25009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long currentMillis = System.currentTimeMillis();
25039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long start = currentMillis - SCHEDULE_ALARM_SLACK;
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final long end = start + (24 * 60 * 60 * 1000);
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentResolver cr = getContext().getContentResolver();
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Time time = new Time();
25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(start);
25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
25109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "runScheduleNextAlarm() start search: " + startTimeStr);
25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Clear old alarms but keep alarms around for a while to prevent
25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // multiple alerts for the same reminder.  The "clearUpToTime'
25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // should be further in the past than the point in time where
25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // we start searching for events (the "start" variable defined above).
25179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long clearUpToTime = currentMillis - CLEAR_OLD_ALARM_THRESHOLD;
25189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.delete("CalendarAlerts", CalendarAlerts.ALARM_TIME + "<" + clearUpToTime, null);
25199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long nextAlarmTime = end;
25219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long alarmTime = CalendarAlerts.findNextAlarmTime(cr, currentMillis);
25229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (alarmTime != -1 && alarmTime < nextAlarmTime) {
25239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            nextAlarmTime = alarmTime;
25249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Extract events from the database sorted by alarm time.  The
25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // alarm times are computed from Instances.begin (whose units
25289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // are milliseconds) and Reminders.minutes (whose units are
25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // minutes).
25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
25319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events whose end time is already in the past.
25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Also, ignore events alarms that we have already scheduled.
25339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
25349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 1: we can add support for the case where Reminders.minutes
25359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // equals -1 to mean use Calendars.minutes by adding a UNION for
25369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that case where the two halves restrict the WHERE clause on
25379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders.minutes != -1 and Reminders.minutes = 1, respectively.
25389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        //
25399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note 2: we have to name "myAlarmTime" different from the
25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // "alarmTime" column in CalendarAlerts because otherwise the
25419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query won't find multiple alarms for the same event.
25429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String query = "SELECT begin-(minutes*60000) AS myAlarmTime,"
25439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " Instances.event_id AS eventId, begin, end,"
25449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " title, allDay, method, minutes"
25459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " FROM Instances INNER JOIN Events"
25469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ON (Events._id = Instances.event_id)"
25479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " INNER JOIN Reminders"
25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ON (Instances.event_id = Reminders.event_id)"
25499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " WHERE method=" + Reminders.METHOD_ALERT
25509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND myAlarmTime>=" + start
25519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND myAlarmTime<=" + nextAlarmTime
25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND end>=" + currentMillis
25539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND 0=(SELECT count(*) from CalendarAlerts CA"
25549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " where CA.event_id=Instances.event_id AND CA.begin=Instances.begin"
25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " AND CA.alarmTime=myAlarmTime)"
25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                + " ORDER BY myAlarmTime,begin,title";
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        acquireInstanceRangeLocked(start, end, false /* don't use minimum expansion windows */);
25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
25619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = db.rawQuery(query, null);
25629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int beginIndex = cursor.getColumnIndex(Instances.BEGIN);
25649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int endIndex = cursor.getColumnIndex(Instances.END);
25659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int eventIdIndex = cursor.getColumnIndex("eventId");
25669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int alarmTimeIndex = cursor.getColumnIndex("myAlarmTime");
25679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int minutesIndex = cursor.getColumnIndex(Reminders.MINUTES);
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
25709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time time = new Time();
25719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.set(nextAlarmTime);
25729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String alarmTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
25739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "nextAlarmTime: " + alarmTimeStr
25749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " cursor results: " + cursor.getCount()
25759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " query: " + query);
25769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (cursor.moveToNext()) {
25799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule all alarms whose alarm time is as early as any
25809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // scheduled alarm.  For example, if the earliest alarm is at
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // 1pm, then we will schedule all alarms that occur at 1pm
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // but no alarms that occur later than 1pm.
25839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Actually, we allow alarms up to a minute later to also
25849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // be scheduled so that we don't have to check immediately
25859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // again after an event alarm goes off.
25869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                alarmTime = cursor.getLong(alarmTimeIndex);
25879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long eventId = cursor.getLong(eventIdIndex);
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int minutes = cursor.getInt(minutesIndex);
25899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long startTime = cursor.getLong(beginIndex);
25909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
25929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    int titleIndex = cursor.getColumnIndex(Events.TITLE);
25939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String title = cursor.getString(titleIndex);
25949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Time time = new Time();
25959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(alarmTime);
25969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
25979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(startTime);
25989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    long endTime = cursor.getLong(endIndex);
26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(endTime);
26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String endTimeStr = time.format(" - %a, %b %d, %Y %I:%M%P");
26029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(currentMillis);
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String currentTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "  looking at id: " + eventId + " " + title
26059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " " + startTime
26069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + startTimeStr + endTimeStr + " alarm: "
26079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + alarmTime + schedTime
26089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " currentTime: " + currentTimeStr);
26099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (alarmTime < nextAlarmTime) {
26129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    nextAlarmTime = alarmTime;
26139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } else if (alarmTime >
26149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                           nextAlarmTime + android.text.format.DateUtils.MINUTE_IN_MILLIS) {
26159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This event alarm (and all later ones) will be scheduled
26169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // later.
26179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    break;
26189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Avoid an SQLiteContraintException by checking if this alarm
26219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // already exists in the table.
26229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (CalendarAlerts.alarmExists(cr, eventId, startTime, alarmTime)) {
26239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (Log.isLoggable(TAG, Log.DEBUG)) {
26249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int titleIndex = cursor.getColumnIndex(Events.TITLE);
26259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        String title = cursor.getString(titleIndex);
26269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Log.d(TAG, "  alarm exists for id: " + eventId + " " + title);
26279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
26289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
26299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Insert this alarm into the CalendarAlerts table
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long endTime = cursor.getLong(endIndex);
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Uri uri = CalendarAlerts.insert(cr, eventId, startTime,
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        endTime, alarmTime, minutes);
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (uri == null) {
26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.e(TAG, "runScheduleNextAlarm() insert into CalendarAlerts table failed");
26379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    continue;
26389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                intent.setData(uri);
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Also include the begin and end time of this event, because
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // we cannot determine that from the Events database table.
26459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, startTime);
26469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                intent.putExtra(android.provider.Calendar.EVENT_END_TIME, endTime);
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    int titleIndex = cursor.getColumnIndex(Events.TITLE);
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String title = cursor.getString(titleIndex);
26509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Time time = new Time();
26519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(alarmTime);
26529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
26539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(startTime);
26549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String startTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(endTime);
26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String endTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    time.set(currentMillis);
26589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String currentTimeStr = time.format(" %a, %b %d, %Y %I:%M%P");
26599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "  scheduling " + title
26609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + startTimeStr  + " - " + endTimeStr + " alarm: " + schedTime
26619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " currentTime: " + currentTimeStr
26629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " uri: " + uri);
26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                PendingIntent sender = PendingIntent.getBroadcast(getContext(),
26659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
26669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
26679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If we scheduled an event alarm, then schedule the next alarm check
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for one minute past that alarm.  Otherwise, if there were no
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event alarms scheduled, then check again in 24 hours.  If a new
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // event is inserted before the next alarm check, then this method
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // will be run again when the new event is inserted.
26799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (nextAlarmTime != Long.MAX_VALUE) {
26809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            scheduleNextAlarmCheck(nextAlarmTime + android.text.format.DateUtils.MINUTE_IN_MILLIS);
26819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
26829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            scheduleNextAlarmCheck(currentMillis + android.text.format.DateUtils.DAY_IN_MILLIS);
26839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Removes the entries in the CalendarAlerts table for alarms that we have
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * scheduled but that have not fired yet. We do this to ensure that we
26899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * don't miss an alarm.  The CalendarAlerts table keeps track of the
26909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * alarms that we have scheduled but the actual alarm list is in memory
26919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and will be cleared if the phone reboots.
26929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We don't need to remove entries that have already fired, and in fact
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * we should not remove them because we need to display the notifications
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * until the user dismisses them.
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * We could remove entries that have fired and been dismissed, but we leave
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * them around for a while because it makes it easier to debug problems.
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Entries that are old enough will be cleaned up later when we schedule
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * new alarms.
27019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
27029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void removeScheduledAlarmsLocked(SQLiteDatabase db) {
27039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (Log.isLoggable(TAG, Log.DEBUG)) {
27049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.d(TAG, "removing scheduled alarms");
27059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        db.delete(CalendarAlerts.TABLE_NAME,
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED, null /* whereArgs */);
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sEventsTable = "Events";
27119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sAttendeesTable = "Attendees";
27129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sRemindersTable = "Reminders";
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sCalendarAlertsTable = "CalendarAlerts";
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static String sExtendedPropertiesTable = "ExtendedProperties";
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int DELETED_EVENTS = 4;
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS = 5;
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_ID = 6;
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES = 7;
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int ATTENDEES_ID = 8;
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS = 9;
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int REMINDERS_ID = 10;
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES = 11;
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EXTENDED_PROPERTIES_ID = 12;
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS = 13;
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_ID = 14;
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDAR_ALERTS_BY_INSTANCE = 15;
27316db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int INSTANCES_BY_DAY = 16;
27326db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE = 17;
27336db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int SYNCSTATE_ID = 18;
27346db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES = 19;
27356db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_ENTITIES_ID = 20;
27366db535b458146a279bebd4a51d56c1bdfc204528Erik    private static final int EVENT_DAYS = 21;
27379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sEventsProjectionMap;
274119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
27439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
27449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
27459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
27479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "instances/when/*/*", INSTANCES);
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "instances/whenbyday/*/*", INSTANCES_BY_DAY);
27496db535b458146a279bebd4a51d56c1bdfc204528Erik        sUriMatcher.addURI("calendar", "instances/groupbyday/*/*", EVENT_DAYS);
27509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "events", EVENTS);
27519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "events/#", EVENTS_ID);
275219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sUriMatcher.addURI("calendar", "event_entities", EVENT_ENTITIES);
275319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sUriMatcher.addURI("calendar", "event_entities/#", EVENT_ENTITIES_ID);
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "calendars", CALENDARS);
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "calendars/#", CALENDARS_ID);
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "deleted_events", DELETED_EVENTS);
27579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "attendees", ATTENDEES);
27589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "attendees/#", ATTENDEES_ID);
27599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "reminders", REMINDERS);
27609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "reminders/#", REMINDERS_ID);
27619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "extendedproperties", EXTENDED_PROPERTIES);
27629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "extendedproperties/#", EXTENDED_PROPERTIES_ID);
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "calendar_alerts", CALENDAR_ALERTS);
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "calendar_alerts/#", CALENDAR_ALERTS_ID);
27659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", "calendar_alerts/by_instance", CALENDAR_ALERTS_BY_INSTANCE);
27669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", SyncStateContentProviderHelper.PATH, SYNCSTATE);
27679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sUriMatcher.addURI("calendar", SyncStateContentProviderHelper.PATH + "/#", SYNCSTATE_ID);
27689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HTML_URI, "htmlUri");
27729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TITLE, "title");
27739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DESCRIPTION, "description");
27759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.STATUS, "eventStatus");
27769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
27779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
27789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTSTART, "dtstart");
27799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DTEND, "dtend");
27809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
27819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.DURATION, "duration");
27829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ALL_DAY, "allDay");
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.VISIBILITY, "visibility");
27849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.TRANSPARENCY, "transparency");
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
27879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RRULE, "rrule");
27889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.RDATE, "rdate");
27899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXRULE, "exrule");
27909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.EXDATE, "exdate");
27919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
27929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
27939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
27949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.LAST_DATE, "lastDate");
27959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
27969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
27979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
27989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
27999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
28009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events.ORGANIZER, "organizer");
28017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        sEventsProjectionMap.put(Events.DELETED, "deleted");
28029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2803982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Attendees, Reminders, CalendarAlerts projection map
28041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
28051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
28061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
28071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
28089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
2809982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.COLOR, "color");
2810982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.ACCESS_LEVEL, "access_level");
2811982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sEventsProjectionMap.put(Calendars.SELECTED, "selected");
28129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.URL, "url");
28139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.TIMEZONE, "timezone");
28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, "ownerAccount");
28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2816982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
2817982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // The Instances are joined with Calendars, so the projection includes the
2818982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // above Calendar columns.
2819982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
2820982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
28211ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._ID, "_id");
28221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ID, "_sync_id");
28231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_VERSION, "_sync_version");
28241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_TIME, "_sync_time");
28251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_LOCAL_ID, "_sync_local_id");
28261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_DIRTY, "_sync_dirty");
28271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT, "_sync_account");
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap.put(Events._SYNC_ACCOUNT_TYPE,
28291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                "_sync_account_type");
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
283119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap = Maps.newHashMap();
283219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HTML_URI, "htmlUri");
283319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TITLE, "title");
283419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, "description");
283519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, "eventLocation");
283619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.STATUS, "eventStatus");
283719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, "selfAttendeeStatus");
283819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.COMMENTS_URI, "commentsUri");
283919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTSTART, "dtstart");
284019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DTEND, "dtend");
284119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DURATION, "duration");
284219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, "eventTimezone");
284319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ALL_DAY, "allDay");
284419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.VISIBILITY, "visibility");
284519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.TRANSPARENCY, "transparency");
284619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, "hasAlarm");
284719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, "hasExtendedProperties");
284819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RRULE, "rrule");
284919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.RDATE, "rdate");
285019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXRULE, "exrule");
285119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.EXDATE, "exdate");
285219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_EVENT, "originalEvent");
285319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, "originalInstanceTime");
285419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, "originalAllDay");
285519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.LAST_DATE, "lastDate");
285619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, "hasAttendeeData");
285719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, "calendar_id");
285819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, "guestsCanInviteOthers");
285919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, "guestsCanModify");
286019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, "guestsCanSeeGuests");
286119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.ORGANIZER, "organizer");
286219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events.DELETED, "deleted");
286319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
286419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
286519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_VERSION, Events._SYNC_VERSION);
286619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_DIRTY, Events._SYNC_DIRTY);
286719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Calendars.URL, "url");
286819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
28699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
28709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
28719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
28729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
28739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
28749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
28759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
28769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
28779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
28789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
28809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
28819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
28829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
28839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
28849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
28859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
28869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
28879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
28899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
28909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
28919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
28929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
28939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
28959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
28969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
28989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
28999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
29009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
29059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make sure that there are no entries for accounts that no longer
29069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * exist. We are overriding this since we need to delete from the
29079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
29087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
29097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
29119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb = mDbHelper.getWritableDatabase();
29139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (mDb == null) return;
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Map<Account, Boolean> accountHasCalendar = Maps.newHashMap();
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Set<Account> validAccounts = Sets.newHashSet();
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
29199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
29209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
29229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (String table : new String[]{"Calendars"}) {
29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Find all the accounts the contacts DB knows about, mark the ones that aren't
29289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
29299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Cursor c = mDb.rawQuery("SELECT DISTINCT " + CalendarDatabaseHelper.ACCOUNT_NAME
29309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                        + ","
29319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                                        + CalendarDatabaseHelper.ACCOUNT_TYPE + " from "
29329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + table, null);
29339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
29349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (c.getString(0) != null && c.getString(1) != null) {
29359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
29379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
29389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
29409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
29429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
29459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "removing data for removed account " + account);
29469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
29479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                mDb.execSQL("DELETE FROM Calendars"
29489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " WHERE " + CalendarDatabaseHelper.ACCOUNT_NAME + "= ? AND "
29499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + CalendarDatabaseHelper.ACCOUNT_TYPE
29509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + "= ?", params);
29519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
29539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
29549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
29559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
29569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (mCalendarClient == null) {
29599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
29609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2963595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /* package */ static boolean readBooleanQueryParameter(Uri uri, String name,
2964595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            boolean defaultValue) {
2965595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        final String flag = getQueryParameter(uri, name);
29669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return flag == null
29679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                ? defaultValue
29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                : (!"false".equals(flag.toLowerCase()) && !"0".equals(flag.toLowerCase()));
29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2971595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    // Duplicated from ContactsProvider2.  TODO: a utility class for shared code
2972595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /**
2973595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff     * A fast re-implementation of {@link Uri#getQueryParameter}
2974595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff     */
2975595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    /* package */ static String getQueryParameter(Uri uri, String parameter) {
2976595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        String query = uri.getEncodedQuery();
2977595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (query == null) {
2978595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            return null;
2979595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
2980595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
2981595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int queryLength = query.length();
2982595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int parameterLength = parameter.length();
2983595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
2984595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        String value;
2985595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int index = 0;
2986595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        while (true) {
2987595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            index = query.indexOf(parameter, index);
2988595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (index == -1) {
2989595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                return null;
2990595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
2991595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
2992595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            index += parameterLength;
2993595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
2994595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (queryLength == index) {
2995595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                return null;
2996595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
2997595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
2998595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            if (query.charAt(index) == '=') {
2999595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                index++;
3000595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                break;
3001595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            }
3002595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3003595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3004595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        int ampIndex = query.indexOf('&', index);
3005595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (ampIndex == -1) {
3006595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            value = query.substring(index);
3007595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
3008595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff            value = query.substring(index, ampIndex);
3009595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
3010595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
3011595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        return Uri.decode(value);
3012595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3013636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff
3014636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
3015636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
3016636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
3017636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
3018636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
3019636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
3020636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
3021636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
3022636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
3023636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
3024636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
3025636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
3026636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
3027636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
3028636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
3029636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
3030636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
3031636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
3032636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
30339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
3034