CalendarProvider2.java revision cad6bc946434363f6ba6fed58bfa818cd6736d21
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
20bf61571797b7b6a390d35f16aad7765ea348e5aeAndy McFaddenimport com.android.calendarcommon.DateException;
2193e0bbb921cce7a5cec355521bc570c03c9d6a1cAndy McFaddenimport com.android.calendarcommon.EventRecurrence;
22bf61571797b7b6a390d35f16aad7765ea348e5aeAndy McFaddenimport com.android.calendarcommon.RecurrenceProcessor;
2393e0bbb921cce7a5cec355521bc570c03c9d6a1cAndy McFaddenimport com.android.calendarcommon.RecurrenceSet;
247be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
257be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
26370f91c0cfe5a5fecaba6120e703f4d2271d2277Erikimport com.google.common.annotations.VisibleForTesting;
27370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
45a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
46a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
49b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
50b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Attendees;
51b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
52b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
53b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
54b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
55b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
56b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.SyncState;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
581edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
59192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
62ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
643b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.io.File;
652ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErikimport java.lang.reflect.Array;
663b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.lang.reflect.Method;
679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
68ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
71dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
72bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFaddenimport java.util.Set;
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
74dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
79b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik * is defined in {@link android.provider.CalendarContract}.
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
848bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
85d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    static final boolean DEBUG_INSTANCES = false;
869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
877be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
88c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String ACCOUNT_SELECTION_PREFIX = Calendars.ACCOUNT_NAME + "=? AND "
89c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            + Calendars.ACCOUNT_TYPE + "=?";
907be45683e367bd6897daf6444b03be938f8f5eaaErik
91f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final boolean PROFILE = false;
929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
938f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
101b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            Events.ORIGINAL_ID,
102c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            Events.ORIGINAL_SYNC_ID,
1039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1049ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
1059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
108b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_ID_INDEX = 3;
109b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_SYNC_ID_INDEX = 4;
1107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
1137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
1147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
119646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
120646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
121646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
122646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
123646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
124646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
125646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
126646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
127646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
128646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
129646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
130646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
131646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
132646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
133646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
134646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
140ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
143f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    private CalendarInstancesHelper mInstancesHelper;
1449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1458ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
146f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // Due to an issue in Calendar Server restricting the length of the name we
147f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // had to strip it down
1488ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1498ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1508ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1518ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1528ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1533443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
154b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + ", " +
155b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTSTART_2445 + ", " +
156b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTEND_2445 + ", " +
1573443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1583443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
159b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
160b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1613443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
162b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
163b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
164b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_UPDATE_EVENT_SET_DIRTY = "UPDATE " +
165b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
166c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            " SET " + Events.DIRTY + "=1" +
167b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
168b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
1692ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    protected static final String SQL_WHERE_ID = Events._ID + "=?";
170b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EVENT_ID = "event_id=?";
1714d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID = Events.ORIGINAL_ID + "=?";
1724d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID = Events.ORIGINAL_ID +
1734d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden            "=? AND " + Events._SYNC_ID + " IS NULL";
174ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
175ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan    private static final String SQL_WHERE_ATTENDEE_BASE =
176ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID
177ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            + " AND " +
178ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
179ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
180b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
181ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " + SQL_WHERE_ATTENDEE_BASE;
182b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
183b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
184b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
185ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID +
186ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            " AND " +
187ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
188b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
189b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
1902ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
191b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
192b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
193b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
1942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
195b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
196b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
197b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
199b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
200b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            Tables.EXTENDED_PROPERTIES + "." + CalendarContract.ExtendedProperties._ID + "=?";
201b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
202b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
2032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                " WHERE " + Calendars.ACCOUNT_NAME + "=? AND " +
2042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    Calendars.ACCOUNT_TYPE + "=?";
205b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
206fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
207fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
208fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
21881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
21981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
22081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
22181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
222b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
22381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
224b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
22581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
22618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
22718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
22818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
22918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
23018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
231b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
23218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
233b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")" + ") LEFT OUTER JOIN " +
23418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
23518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
236b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Attendees.EVENT_ID + "=" +
23718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
238b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
23918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
240b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
241b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.START_DAY + "<=? AND " +
242b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END_DAY + ">=?";
24381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
244b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
245b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.BEGIN + "<=? AND " +
246b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END + ">=?";
2479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
2552ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * The sort order is: events with an earlier start time occur first and if
2562ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the start times are the same, then events with a later end time occur
2572ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * first. The later end time is ordered first so that long-running events in
2582ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the calendar views appear first. If the start and end times of two events
2592ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * are the same then we sort alphabetically on the title. This isn't
2602ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * required for correctness, it just adds a nice touch.
2612ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
2622ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
2632ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
2642ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
2652ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * A regex for describing how we split search queries into tokens. Keeps
2662ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * quoted phrases as one token. "one \"two three\"" ==> ["one" "two three"]
267dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
268dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
269dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
270dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
271dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
272dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
273dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
274dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
275dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
276dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
27781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
278dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
279dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
280dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
281dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
282dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
283dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
284dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
285dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
286dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
28781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
28818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
28918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
29018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
29118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
29218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
293b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_EMAIL + ")";
29418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
29518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
29618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
29718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
29818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
29918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
300b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_NAME + ")";
30118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
30281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
303b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.TITLE,
304b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.DESCRIPTION,
305b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.EVENT_LOCATION,
30618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
30718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
30881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
30981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
310a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
311a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
312a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
313a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
314a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
315a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
316a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
317a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
318a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
319a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
320a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
321a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
322dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
323dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
324dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
325dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
326dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
327bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Set of columns allowed to be altered when creating an exception to a recurring event. */
328bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final HashSet<String> ALLOWED_IN_EXCEPTION = new HashSet<String>();
329bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    static {
330bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // _id, _sync_account, _sync_account_type, dirty, _sync_mark, calendar_id
331bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events._SYNC_ID);
332bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA1);
333bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA7);
33402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA3);
335bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.TITLE);
336bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_LOCATION);
337bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DESCRIPTION);
338bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.STATUS);
339c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SELF_ATTENDEE_STATUS);
34002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA6);
341bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DTSTART);
342c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // dtend -- set from duration as part of creating the exception
343bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_TIMEZONE);
344bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_END_TIMEZONE);
345bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DURATION);
346bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ALL_DAY);
347bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ACCESS_LEVEL);
348bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.AVAILABILITY);
349bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ALARM);
350bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_EXTENDED_PROPERTIES);
351bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RRULE);
352bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RDATE);
353bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXRULE);
354bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXDATE);
355bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_SYNC_ID);
356bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_INSTANCE_TIME);
357bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // originalAllDay, lastDate
358bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ATTENDEE_DATA);
359bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_MODIFY);
360bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_INVITE_OTHERS);
361bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_SEE_GUESTS);
362bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORGANIZER);
363bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // deleted, original_id, alerts
364bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
365bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
366bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Don't clone these from the base event into the exception event. */
367bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final String[] DONT_CLONE_INTO_EXCEPTION = {
368bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events._SYNC_ID,
369bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA1,
37002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA2,
37102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA3,
37202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA4,
37302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA5,
37402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA6,
375bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA7,
37602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA8,
377c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA9,
378c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA10,
379bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    };
380bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
381bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** set to 'true' to enable debug logging for recurrence exception code */
382bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final boolean DEBUG_EXCEPTION = false;
383bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
384dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
385e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
386e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
3878bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
3888bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
389420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
390420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected CalendarAlarmManager mCalendarAlarm;
391a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
392a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
393a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
394a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
395dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
396a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
397a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
398a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
399dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
400dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
401dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
402dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
403dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
404a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
405a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
406a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
407a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
421420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
425420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
427420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4388bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4398bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4408bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4418bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
442e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
443e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
444e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
445e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
446e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
447e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
448e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
4498bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4508bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
4539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
454ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
455ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
456ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
457f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
458f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
459f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
460ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
461ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
462ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
464ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
4658bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
4668bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
467dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
468e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
469e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
470ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
471ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4734caf8d015918f619a67d321a152f150a01022717Andy McFadden        mMetaData = new MetaData(mDbHelper);
4744caf8d015918f619a67d321a152f150a01022717Andy McFadden        mInstancesHelper = new CalendarInstancesHelper(mDbHelper, mMetaData);
4754caf8d015918f619a67d321a152f150a01022717Andy McFadden
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
4809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
4819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
4829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
4849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
4859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
4869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
487e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
4889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
489ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
490ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
491420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // This is pulled out for testing
492420b7fb569773ae573fbe90c3a9c522d4c368863Erik        initCalendarAlarm();
493e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
494e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
4958bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
4979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
499420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected void initCalendarAlarm() {
500420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm = getOrCreateCalendarAlarmManager();
501420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.getScheduleNextAlarmWakeLock();
502e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
503e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
504e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
505420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mCalendarAlarm == null) {
506420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm = new CalendarAlarmManager(mContext);
507e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
508420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mCalendarAlarm;
509e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
510e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
511ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
512ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
513ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
514ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
515ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
516ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
517ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
518ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
519ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
520ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
521ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
522ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
523ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
524ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
525ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
526ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
52764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void verifyAccounts() {
52864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
52964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        removeStaleAccounts(AccountManager.get(getContext()).getAccounts());
53064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
53164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
53264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
5339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
536315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
5379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
5399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
5419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
5449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
5459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
5469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
547ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
5489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
552315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
553315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
554315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
555315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
556315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
557315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
558315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
559315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
5609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
5619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
5629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
563315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
564ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
565315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
566315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
567a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
568315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
569315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
570315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
571ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
572315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
573315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
574ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
575315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
576ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
577ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
578ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
579ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
580420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.rescheduleMissedAlarms();
581ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
582ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
583f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
584f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
585f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
586ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
587ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
588ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
589ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
590ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
591f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
592f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
593f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
594ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
5959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
596ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
597ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
598315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
599ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
600ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
6013443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
602ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
603315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
604ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
605ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
606ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
607ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
608ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
609ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
610ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
6113443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
6123443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
613ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
614ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
615ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
616ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
617ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6183443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
619f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
620f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
621f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
622f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
623f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
624f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
625f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
626ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6273443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
628ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
629ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
630ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
631ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
632ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
633ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
634ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
635ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
636ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
637ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
638ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
639f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
640f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
641f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
642ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
643ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
644ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
645ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
646ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
647ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
648f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
649f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
650f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
651ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
652ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
653ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
654ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
655ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
656ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
657ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
659ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
660b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
661b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
662ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
663b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
664dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
665ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
666ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
667ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
668ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
669ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
670ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
671ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
672ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
673ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
674ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
675ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
676f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
677f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
678f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
679ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
680ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
6819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
682ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
683ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
684ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
685ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
686315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
687315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
688ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
689ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
690ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
691ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
692ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
69325e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
694ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
695315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
696315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
697ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
698ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
699f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
700f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
701f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
702ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
703ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
704ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
705315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
706315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
707315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
708315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
709315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
710ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
7119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
7129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
7139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
7149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
715315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
716315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7221f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7251f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7261f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7271f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7281f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7291f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7301f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
7312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* selection */, null,
7322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* sort */,
733d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
734315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
7352ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    instancesTimezone, isHomeTimezone());
7361f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
7371f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
7381f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
7391f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7401f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
7419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
742420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.rescheduleMissedAlarms();
7439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
747b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
7489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
7499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
750b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        mContentResolver.notifyChange(CalendarContract.CONTENT_URI, null, syncToNetwork);
7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
7549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
756ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
757ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
7639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
7649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
765315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
7669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
7689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
7699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
7709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
7719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
7729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
7741ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
7769ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
7779ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
7801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
782636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
783b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
7849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
78519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
78619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
78719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
78819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
7899ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
7909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
79119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
79219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
79319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
79419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
795636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
796b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
79719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
79819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
80043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
801b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
8029ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
80543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
806b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
807636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
808b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
8139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
8149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
8169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
8189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
826315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
8272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceQuery(qb, begin, end, projection, selection, selectionArgs,
8282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        sortOrder, match == INSTANCES_BY_DAY, false /* don't force an expansion */,
829315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
83081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
83181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
83281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
83381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
83481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
83581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
83681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
83781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
83881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
83981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
84081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
84181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
84281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
84381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
844315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
84581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
84681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
8472ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceSearchQuery(qb, begin, end, query, projection, selection,
8482ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        selectionArgs, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
849315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8506db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
8519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
8559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
8579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
8619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
8639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
865315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
866315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
867315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
86902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
871ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan                qb.appendWhere(SQL_WHERE_ATTENDEE_BASE);
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
87402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
876636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
877b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
880b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
88302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
885636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
886b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
8879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
889b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
8909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
891b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
894b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
896b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
8979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
8989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
900b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
902636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
903b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
906b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
909b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
910636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
911b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
913315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
914b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
915315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
916315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
9239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
928ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
92939c65e5716e21e863d8de587d139dae85f99422fFred Quintana        if (projection != null && projection.length == 1
93039c65e5716e21e863d8de587d139dae85f99422fFred Quintana                && BaseColumns._COUNT.equals(projection[0])) {
93139c65e5716e21e863d8de587d139dae85f99422fFred Quintana            qb.setProjectionMap(sCountProjectionMap);
93239c65e5716e21e863d8de587d139dae85f99422fFred Quintana        }
93339c65e5716e21e863d8de587d139dae85f99422fFred Quintana
934ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
935ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
936ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
937ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
938ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
939ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
940ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
941ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
9429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
9439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
9449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
946b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            c.setNotificationUri(mContentResolver, CalendarContract.Events.CONTENT_URI);
9479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
9529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
9539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
9549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
9619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
962d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
963315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
964315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
9669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
9679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
9682ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            long rangeEnd, String[] projection, String selection, String[] selectionArgs,
9692ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String sort, boolean searchByDay, boolean forceExpansion,
9702ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
9719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
97281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
9739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
977315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
9809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
983315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
984315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
985b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
9869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
9879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
988315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
989315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
990b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
9919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9922ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
9932ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = new String[] {String.valueOf(rangeEnd),
9948335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
9952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
9962ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
9972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
9982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
9992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
10002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
10012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10028335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
10037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
10049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
100681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
10072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * Combine a set of arrays in the order they are passed in. All arrays must
10082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * be of the same type.
10092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
10102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static <T> T[] combine(T[]... arrays) {
10112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (arrays.length == 0) {
10122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            throw new IllegalArgumentException("Must supply at least 1 array to combine");
10132ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int totalSize = 0;
10162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            totalSize += array.length;
10182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10192ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10202ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        T[] finalArray = (T[]) (Array.newInstance(arrays[0].getClass().getComponentType(),
10212ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                totalSize));
10222ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int currentPos = 0;
10242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            int length = array.length;
10262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            System.arraycopy(array, 0, finalArray, currentPos, length);
10272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            currentPos += array.length;
10282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        return finalArray;
10302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    }
10312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
1033dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1034dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1035dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1036dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1037dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1038dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1039dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1040dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1041dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1042dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1043dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
104481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1045dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1046dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
104781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
104881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
104981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
105081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
105181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
105281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1053dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1054dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1055dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1056dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1057dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1058dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1059dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1060dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1061dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1062dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1063dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1064dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1065dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1066dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
106781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
106881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
106981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
107081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
107181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
107281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
107381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
107481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
107581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
107681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
107781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
107881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
107981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
108081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
108181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
108281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
108381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
108481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
108581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
1086cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * approximation of full-text search across multiple columns.  The set
1087cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * of columns is specified by the SEARCH_COLUMNS constant.
1088cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * <p>
1089cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * Note the "WHERE" token isn't part of the returned string.  The value
1090cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * may be passed into a query as the "HAVING" clause.
109181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
109281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
109381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
109481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
109581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
109681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
109781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
109881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
109981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
110081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
110181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
110281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1103dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1104dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1105dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
110681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
110781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
110881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
110981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
111018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
111118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
111218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
111318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
111481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
111581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
111681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
111781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
111881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
111981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
112018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
112118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
112281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
112318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
112418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
112518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
112681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1127f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1128f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
112918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
113081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
113181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
113281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
113381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
113481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
113581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
113681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
11372ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String selection, String[] selectionArgs, String sort, boolean searchByDay,
11382ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
113918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
114081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
114181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1142dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
11432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
11442ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
11452ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
11462ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
11472ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
11482ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
11492ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
11502ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
115118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
115218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1153dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1154dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
115581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
115681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
115781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1158315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
115981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
116081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
116181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
116281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
116381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
116418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
116518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
116656292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
116756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1168315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1169315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1170315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
117156292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1172b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
117381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
117481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
117518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
117618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
117756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
117856292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1179315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1180315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1181315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
118256292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1183b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
118481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
118581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
118618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
1187cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden                Tables.EVENTS + "." + Instances._ID /* groupBy */,
1188cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden                searchWhere /* having */, sort);
118981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
119081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
11916db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1192315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1193315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
119481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
11956db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
119643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
119743556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1198315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1199192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
120043556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
120143556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1202192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
120343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1204315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1205315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1206b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
12078335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
12088335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
12098335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
12106db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
12119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * table.  Acquires the database lock and calls
12169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #acquireInstanceRangeLocked(long, long, boolean, boolean, String, boolean)}.
12179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1221d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1222315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1223315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1225d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1226315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1227315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
12289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
12299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1230315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1231315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
12329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
12339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
12349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
12359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
12419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1245315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1246315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1247315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1249420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1250315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
12519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
12529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1254d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (DEBUG_INSTANCES) {
1255d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            Log.d(TAG + "-i", "acquireInstanceRange begin=" + begin + " end=" + end +
1256d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    " useMin=" + useMinimumExpansionWindow + " force=" + forceExpansion);
1257d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1258d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1259315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1260315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1261315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1263315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
12649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
12659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
12669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
12679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
12689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
12739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
12749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
12799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
12819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1282315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1283315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1284315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1285315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1286315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1287315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
12897be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
12907be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
12917be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
12927be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1293315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1294315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1295315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1296d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
1297d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1298d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "Wiping instances and expanding from scratch");
1299d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1300d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1302b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1303f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13046db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1307f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1308315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1309315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1311315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
13127be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
13137be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
13147be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
13157be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
13167be45683e367bd6897daf6444b03be938f8f5eaaErik
13177be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1318315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
13197be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
13207be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
13217be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
13227be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1323315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1339d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1340d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "instances are already expanded");
1341d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1342f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1352f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1359f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1364315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
13689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
13699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
13709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
13719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
13729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
13739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
13749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
13759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
13769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
13789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
13876db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
138948587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
139048587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1391315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1392315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1398b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    /**
1399b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     * Determines if the event is recurrent, based on the provided values.
1400b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     */
1401b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalId,
1402b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            String originalSyncId) {
1403b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden        return (!TextUtils.isEmpty(rrule) ||
1404b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(rdate) ||
1405b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalId) ||
1406b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalSyncId));
14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1409646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1410646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1411d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
1412646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1413d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * corrects the fields DTSTART, DTEND, and DURATION if necessary.
1414646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1415d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values The values to check and correct
1416d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param modValues Any updates will be stored here.  This may be the same object as
1417d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   <strong>values</strong>.
1418646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1419646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1420d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean fixAllDayTime(ContentValues values, ContentValues modValues) {
1421499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        Integer allDayObj = values.getAsInteger(Events.ALL_DAY);
1422499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        if (allDayObj == null || allDayObj == 0) {
1423d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            return false;
1424d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1425d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1426646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1427646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1428d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtstart = values.getAsLong(Events.DTSTART);
1429d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtend = values.getAsLong(Events.DTEND);
1430d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String duration = values.getAsString(Events.DURATION);
1431d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Time time = new Time();
1432d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String tempValue;
1433d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1434d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // Change dtstart so h,m,s are 0 if necessary.
1435d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.clear(Time.TIMEZONE_UTC);
1436d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.set(dtstart.longValue());
1437d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1438d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.hour = 0;
1439d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.minute = 0;
1440d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.second = 0;
1441d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            modValues.put(Events.DTSTART, time.toMillis(true));
1442d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            neededCorrection = true;
1443d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1444d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1445d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // If dtend exists for this event make sure it's h,m,s are 0.
1446d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (dtend != null) {
1447646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1448d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.set(dtend.longValue());
1449646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1450646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1451646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1452646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1453d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                dtend = time.toMillis(true);
1454d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DTEND, dtend);
1455646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1456646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1457d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1458646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1459d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (duration != null) {
1460d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            int len = duration.length();
1461d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1462d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             * in the seconds format, and if so converts it to days.
1463d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             */
1464d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (len == 0) {
1465d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = null;
1466d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else if (duration.charAt(0) == 'P' &&
1467d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    duration.charAt(len - 1) == 'S') {
1468d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int seconds = Integer.parseInt(duration.substring(1, len - 1));
1469d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1470d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = "P" + days + "D";
1471d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DURATION, duration);
1472d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                neededCorrection = true;
1473646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1474646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1475d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1476646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1477646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1478646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1479bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1480bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1481bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Determines whether the strings in the set name columns that may be overridden
1482bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * when creating a recurring event exception.
1483bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1484bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This uses a white list because it screens out unknown columns and is a bit safer to
1485bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * maintain than a black list.
1486bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1487bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private void checkAllowedInException(Set<String> keys) {
1488bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        for (String str : keys) {
1489bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!ALLOWED_IN_EXCEPTION.contains(str.intern())) {
1490bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                throw new IllegalArgumentException("Exceptions can't overwrite " + str);
1491bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1492bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1493bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1494bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1495bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
149632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * Splits a recurrent event at a specified instance.  This is useful when modifying "this
149732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * and all future events".
149832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
149932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence rule has a COUNT specified, we need to split that at the point of the
150032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * exception.  If the exception is instance N (0-based), the original COUNT is reduced
150132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * to N, and the exception's COUNT is set to (COUNT - N).
150232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
150332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence doesn't have a COUNT, we need to update or introduce an UNTIL value,
150432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * so that the original recurrence will end just before the exception instance.  (Note
150532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * that UNTIL dates are inclusive.)
150632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
150732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * This should not be used to update the first instance ("update all events" action).
1508bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
150932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @param values The original event values; must include EVENT_TIMEZONE and DTSTART.
151032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        The RRULE value may be modified (with the expectation that this will propagate
151132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        into the exception event).
1512bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param endTimeMillis The time before which the event must end (i.e. the start time of the
1513bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *        exception event instance).
151432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @return Values to apply to the original event.
1515bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1516bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static ContentValues setRecurrenceEnd(ContentValues values, long endTimeMillis) {
151732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        boolean origAllDay = values.getAsBoolean(Events.ALL_DAY);
151832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        String origRrule = values.getAsString(Events.RRULE);
1519bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
152032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        EventRecurrence origRecurrence = new EventRecurrence();
152132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        origRecurrence.parse(origRrule);
1522bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
152332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        // Get the start time of the first instance in the original recurrence.
152432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        long startTimeMillis = values.getAsLong(Events.DTSTART);
1525bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time dtstart = new Time();
1526bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.timezone = values.getAsString(Events.EVENT_TIMEZONE);
152732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        dtstart.set(startTimeMillis);
1528bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1529bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ContentValues updateValues = new ContentValues();
153032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
153132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        if (origRecurrence.count > 0) {
153232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            /*
153332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * Generate the full set of instances for this recurrence, from the first to the
153432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * one just before endTimeMillis.  The list should never be empty, because this method
153532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * should not be called for the first instance.  All we're really interested in is
153632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * the *number* of instances found.
153732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             */
153832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceSet recurSet = new RecurrenceSet(values);
153932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceProcessor recurProc = new RecurrenceProcessor();
154032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            long[] recurrences;
154132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            try {
154232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis);
154332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            } catch (DateException de) {
154432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException(de);
154532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
154632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
154732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (recurrences.length == 0) {
154832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException("can't use this method on first instance");
154932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
155032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
155132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            EventRecurrence excepRecurrence = new EventRecurrence();
155232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            excepRecurrence.parse(origRrule);  // TODO: add/use a copy constructor to EventRecurrence
155332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            excepRecurrence.count -= recurrences.length;
155432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            values.put(Events.RRULE, excepRecurrence.toString());
155532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
155632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.count = recurrences.length;
155732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
155832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        } else {
155932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            Time untilTime = new Time();
156032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
156132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // The "until" time must be in UTC time in order for Google calendar
156232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // to display it properly. For all-day events, the "until" time string
156332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // must include just the date field, and not the time field. The
156432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // repeating events repeat up to and including the "until" time.
156532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.timezone = Time.TIMEZONE_UTC;
156632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
156732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // Subtract one second from the exception begin time to get the "until" time.
156832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis)
156932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (origAllDay) {
157032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.hour = untilTime.minute = untilTime.second = 0;
157132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.allDay = true;
157232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.normalize(false);
157332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
157432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // This should no longer be necessary -- DTSTART should already be in the correct
157532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // format for an all-day event.
157632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.hour = dtstart.minute = dtstart.second = 0;
157732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.allDay = true;
157832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.timezone = Time.TIMEZONE_UTC;
157932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
158032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.until = untilTime.format2445();
158132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        }
158232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
158332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        updateValues.put(Events.RRULE, origRecurrence.toString());
1584bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.DTSTART, dtstart.normalize(true));
1585bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        return updateValues;
1586bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1587bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1588bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1589bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Handles insertion of an exception to a recurring event.
1590bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1591bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * There are two modes, selected based on the presence of "rrule" in modValues:
1592bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <ol>
1593bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Create a single instance exception ("modify current event only").
1594bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Cap the original event, and create a new recurring event ("modify this and all
1595bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * future events").
1596bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * </ol>
1597bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This may be used for "modify all instances of the event" by simply selecting the
1598bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * very first instance as the exception target.  In that case, the ID of the "new"
1599bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * exception event will be the same as the originalEventId.
1600bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1601bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param originalEventId The _id of the event to be modified
1602bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param modValues Event columns to update
1603c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden     * @param callerIsSyncAdapter Set if the content provider client is the sync adapter
1604bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return the ID of the new "exception" event, or -1 on failure
1605bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1606c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden    private long handleInsertException(long originalEventId, ContentValues modValues,
1607c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            boolean callerIsSyncAdapter) {
1608bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (DEBUG_EXCEPTION) {
1609bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            Log.i(TAG, "RE: values: " + modValues.toString());
1610bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1611bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1612bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Make sure they have specified an instance via originalInstanceTime.
1613bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Long originalInstanceTime = modValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1614bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (originalInstanceTime == null) {
1615bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            throw new IllegalArgumentException("Exceptions must specify " +
1616bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Events.ORIGINAL_INSTANCE_TIME);
1617bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1618bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1619bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Check for attempts to override values that shouldn't be touched.
1620bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        checkAllowedInException(modValues.keySet());
1621bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1622c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // If this isn't the sync adapter, set the "dirty" flag in any Event we modify.
1623c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        if (!callerIsSyncAdapter) {
1624c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            modValues.put(Events.DIRTY, true);
1625c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        }
1626c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1627bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Wrap all database accesses in a transaction.
1628bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        mDb.beginTransaction();
1629bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Cursor cursor = null;
1630bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        try {
1631bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: verify that there's an instance corresponding to the specified time
1632bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (does this matter? it's weird, but not fatal?)
1633bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1634bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Grab the full set of columns for this event.
1635bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor = mDb.query(Tables.EVENTS, null /* columns */,
1636bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    SQL_WHERE_ID, new String[] { String.valueOf(originalEventId) },
1637bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    null /* groupBy */, null /* having */, null /* sortOrder */);
1638bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor.getCount() != 1) {
1639bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event ID " + originalEventId + " lookup failed (count is " +
1640bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        cursor.getCount() + ")");
1641bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1642bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1643bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //DatabaseUtils.dumpCursor(cursor);
1644bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1645bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1646bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * Verify that the original event is in fact a recurring event by checking for the
1647bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * presence of an RRULE.  If it's there, we assume that the event is otherwise
1648bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * properly constructed (e.g. no DTEND).
1649bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1650bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor.moveToFirst();
1651bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int rruleCol = cursor.getColumnIndex(Events.RRULE);
1652bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (TextUtils.isEmpty(cursor.getString(rruleCol))) {
1653bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event has no rrule");
1654bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1655bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1656bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (DEBUG_EXCEPTION) {
1657bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.d(TAG, "RE: old RRULE is " + cursor.getString(rruleCol));
1658bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1659bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1660bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Verify that the original event is not itself a (single-instance) exception.
1661bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int originalIdCol = cursor.getColumnIndex(Events.ORIGINAL_ID);
1662bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!TextUtils.isEmpty(cursor.getString(originalIdCol))) {
1663bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event is an exception");
1664bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1665bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1666bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1667bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createSingleException = TextUtils.isEmpty(modValues.getAsString(Events.RRULE));
1668bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1669bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: check for the presence of an existing exception on this event+instance?
1670bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       The caller should be modifying that, not creating another exception.
1671bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (Alternatively, we could do that for them.)
1672bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1673bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Create a new ContentValues for the new event.  Start with the original event,
1674bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // and drop in the new caller-supplied values.  This will set originalInstanceTime.
1675bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            ContentValues values = new ContentValues();
1676bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
1677bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1678b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            // TODO: if we're changing this to an all-day event, we should ensure that
1679b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       hours/mins/secs on DTSTART are zeroed out (before computing DTEND).
1680b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       See fixAllDayTime().
1681b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1682bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createNewEvent = true;
1683bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createSingleException) {
1684bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1685bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Save a copy of a few fields that will migrate to new places.
1686bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1687bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _id = values.getAsString(Events._ID);
1688bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _sync_id = values.getAsString(Events._SYNC_ID);
1689bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1690bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1691bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1692bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Wipe out some fields that we don't want to clone into the exception event.
1693bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1694bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                for (String str : DONT_CLONE_INTO_EXCEPTION) {
1695bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(str);
1696bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1697bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1698bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1699bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Merge the new values on top of the existing values.  Note this sets
1700bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * originalInstanceTime.
1701bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1702bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.putAll(modValues);
1703bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1704bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1705bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Copy some fields to their "original" counterparts:
1706bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _id --> original_id
1707bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _sync_id --> original_sync_id
1708bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   allDay --> originalAllDay
1709bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1710bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this event hasn't been sync'ed with the server yet, the _sync_id field will
1711bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * be null.  We will need to fill original_sync_id in later.  (May not be able to
1712bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * do it right when our own _sync_id field gets populated, because the order of
1713bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * events from the server may not be what we want -- could update the exception
1714bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * before updating the original event.)
1715bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1716bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * _id is removed later (right before we write the event).
1717bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1718bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ID, _id);
1719bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_SYNC_ID, _sync_id);
1720bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ALL_DAY, allDay);
1721bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1722bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // Mark the exception event status as "tentative", unless the caller has some
1723bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // other value in mind (like STATUS_CANCELED).
1724bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (!values.containsKey(Events.STATUS)) {
1725bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
1726bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1727bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1728bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // We're converting from recurring to non-recurring.  Clear out RRULE and replace
1729bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // DURATION with DTEND.
1730c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                values.remove(Events.RRULE);
1731bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1732bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Duration duration = new Duration();
1733bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String durationStr = values.getAsString(Events.DURATION);
1734bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                try {
1735bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    duration.parse(durationStr);
1736bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } catch (Exception ex) {
1737bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // NullPointerException if the original event had no duration.
1738bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // DateException if the duration was malformed.
1739bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Bad duration in recurring event: " + durationStr, ex);
1740bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1741bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1742bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1743c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1744c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * We want to compute DTEND as an offset from the start time of the instance.
1745c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If the caller specified a new value for DTSTART, we want to use that; if not,
1746c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the DTSTART in "values" will be the start time of the first instance in the
1747c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * recurrence, so we want to replace it with ORIGINAL_INSTANCE_TIME.
1748c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1749c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                long start;
1750c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.DTSTART)) {
1751c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.DTSTART);
1752c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                } else {
1753c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1754c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    values.put(Events.DTSTART, start);
1755c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1756bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.DTEND, start + duration.getMillis());
1757bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1758c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    Log.d(TAG, "RE: ORIG_INST_TIME=" + start +
1759c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            ", duration=" + duration.getMillis() +
1760bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            ", generated DTEND=" + values.getAsLong(Events.DTEND));
1761bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
176285c09a31bcc3a18e173428bf7b628cec2834bebcAndy McFadden                values.remove(Events.DURATION);
1763bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1764bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1765bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * We're going to "split" the recurring event, making the old one stop before
1766bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * this instance, and creating a new recurring event that starts here.
1767bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1768bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * No need to fill out the "original" fields -- the new event is not tied to
1769bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * the previous event in any way.
1770bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1771bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this is the first event in the series, we can just update the existing
1772bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * event with the values.
1773bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1774bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean canceling = (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
1775bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1776bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (originalInstanceTime.equals(values.getAsLong(Events.DTSTART))) {
1777bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1778bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Update fields in the existing event.  Rather than use the merged data
1779bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * from the cursor, we just do the update with the new value set after
1780bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * removing the ORIGINAL_INSTANCE_TIME entry.
1781bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1782bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (canceling) {
1783bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        // TODO: should we just call deleteEventInternal?
1784bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "Note: canceling entire event via exception call");
1785bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1786bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1787bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: updating full event");
1788bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1789ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    if (!validateRecurrenceRule(modValues)) {
1790ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        throw new IllegalArgumentException("Invalid recurrence rule: " +
1791ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                                values.getAsString(Events.RRULE));
1792ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    }
1793bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    modValues.remove(Events.ORIGINAL_INSTANCE_TIME);
1794bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
1795bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1796bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    createNewEvent = false; // skip event creation and related-table cloning
1797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } else {
1798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: splitting event");
1800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
180332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Cap the original event so it ends just before the target instance.  In
180432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * some cases (nonzero COUNT) this will also update the RRULE in "values",
180532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * so that the exception we're creating terminates appropriately.  If a
180632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * new RRULE was specified by the caller, the new rule will overwrite our
180732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * changes when we merge the new values in below (which is the desired
180832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * behavior).
1809bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1810bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    ContentValues splitValues = setRecurrenceEnd(values, originalInstanceTime);
1811bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, splitValues, SQL_WHERE_ID,
1812bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1813bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1814bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
181532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Prepare the new event.  We remove originalInstanceTime, because we're now
1816bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * creating a new event rather than an exception.
1817bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     *
1818bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * We're always cloning a non-exception event (we tested to make sure the
1819bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * event doesn't specify original_id, and we don't allow original_id in the
1820bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * modValues), so we shouldn't end up creating a new event that looks like
1821bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * an exception.
1822bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1823bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.putAll(modValues);
1824bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(Events.ORIGINAL_INSTANCE_TIME);
1825bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1826c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            }
1827bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1828bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            long newEventId;
1829bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createNewEvent) {
1830bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.remove(Events._ID);      // don't try to set this explicitly
1831be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
1832be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(values, null);
1833be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
1834be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(values);
1835be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
1836bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1837bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = mDb.insert(Tables.EVENTS, null, values);
1838bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (newEventId < 0) {
1839bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Unable to add exception to recurring event");
1840bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Values: " + values);
1841bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1842bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1843bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1844bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.d(TAG, "RE: new ID is " + newEventId);
1845bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1846bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1847b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                // TODO: do we need to do something like this?
1848b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                //updateEventRawTimesLocked(id, updatedValues);
1849b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1850b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1851b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Force re-computation of the Instances associated with the recurrence event.
1852b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1853b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, newEventId, true, mDb);
1854b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1855bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1856bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Some of the other tables (Attendees, Reminders, ExtendedProperties) reference
1857c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the Event ID.  We need to copy the entries from the old event, filling in the
1858c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * new event ID, so that somebody doing a SELECT on those tables will find
1859c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * matching entries.
1860bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1861bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                CalendarDatabaseHelper.copyEventRelatedTables(mDb, newEventId, originalEventId);
1862c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1863c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1864c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If we modified Event.selfAttendeeStatus, we need to keep the corresponding
1865c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * entry in the Attendees table in sync.
1866c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1867c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
1868c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    /*
1869c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * Each Attendee is identified by email address.  To find the entry that
1870c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * corresponds to "self", we want to compare that address to the owner of
1871c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * the Calendar.  We're expecting to find one matching entry in Attendees.
1872c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     */
1873c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    long calendarId = values.getAsLong(Events.CALENDAR_ID);
1874c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    cursor = mDb.query(Tables.CALENDARS, new String[] { Calendars.OWNER_ACCOUNT },
1875c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            SQL_WHERE_ID, new String[] { String.valueOf(calendarId) },
1876c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            null /* groupBy */, null /* having */, null /* sortOrder */);
1877c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    if (!cursor.moveToFirst()) {
1878c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        Log.w(TAG, "Can't get calendar account_name for calendar " + calendarId);
1879c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    } else {
1880c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        String accountName = cursor.getString(0);
1881c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        ContentValues attValues = new ContentValues();
1882c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        attValues.put(Attendees.ATTENDEE_STATUS,
1883c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                modValues.getAsString(Events.SELF_ATTENDEE_STATUS));
1884c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1885c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (DEBUG_EXCEPTION) {
1886c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.d(TAG, "Updating attendee status for event=" + newEventId +
1887c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    " name=" + accountName + " to " +
1888c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    attValues.getAsString(Attendees.ATTENDEE_STATUS));
1889c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1890c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        int count = mDb.update(Tables.ATTENDEES, attValues,
1891c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
1892c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                new String[] { String.valueOf(newEventId), accountName });
1893b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                        if (count != 1 && count != 2) {
1894b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // We're only expecting one matching entry.  We might briefly see
1895b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // two during a server sync.
1896b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            Log.e(TAG, "Attendee status update on event=" + newEventId +
1897b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                    " name=" + accountName + " touched " + count + " rows");
1898b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            if (false) {
1899b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                // This dumps PII in the log, don't ship with it enabled.
1900b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                Cursor debugCursor = mDb.query(Tables.ATTENDEES, null,
1901b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        Attendees.EVENT_ID + "=? AND " +
1902b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                            Attendees.ATTENDEE_EMAIL + "=?",
1903b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        new String[] { String.valueOf(newEventId), accountName },
1904b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        null, null, null);
1905b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                DatabaseUtils.dumpCursor(debugCursor);
1906b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            }
1907b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            throw new RuntimeException("Status update WTF");
1908c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1909c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    }
1910c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    cursor.close();
1911c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1912bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1913b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1914b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Update any Instances changed by the update to this Event.
1915b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1916b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, originalEventId, false, mDb);
1917bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = originalEventId;
1918bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1919bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1920bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.setTransactionSuccessful();
1921bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            return newEventId;
1922bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        } finally {
1923bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor != null) {
1924bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                cursor.close();
1925bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1926bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.endTransaction();
1927bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1928bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1929bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1930222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    /**
1931222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Fills in the originalId column for previously-created exceptions to this event.  If
1932222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this event is not recurring or does not have a _sync_id, this does nothing.
1933222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
1934222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * The server might send exceptions before the event they refer to.  When
1935222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this happens, the originalId field will not have been set in the
1936222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * exception events (it's the recurrence events' _id field, so it can't be
1937222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * known until the recurrence event is created).  When we add a recurrence
1938222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * event with a non-empty _sync_id field, we write that event's _id to the
1939222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * originalId field of any events whose originalSyncId matches _sync_id.
1940222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
1941222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Note _sync_id is only expected to be unique within a particular calendar.
1942222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     *
1943222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param id The ID of the Event
1944222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param values Values for the Event being inserted
1945222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     */
1946222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    private void backfillExceptionOriginalIds(long id, ContentValues values) {
1947222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String syncId = values.getAsString(Events._SYNC_ID);
1948222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rrule = values.getAsString(Events.RRULE);
1949222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rdate = values.getAsString(Events.RDATE);
1950222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String calendarId = values.getAsString(Events.CALENDAR_ID);
1951222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
1952222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        if (TextUtils.isEmpty(syncId) || TextUtils.isEmpty(calendarId) ||
1953222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                (TextUtils.isEmpty(rrule) && TextUtils.isEmpty(rdate))) {
1954222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            // Not a recurring event, or doesn't have a server-provided sync ID.
1955222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            return;
1956222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        }
1957222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
1958222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        ContentValues originalValues = new ContentValues();
1959222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        originalValues.put(Events.ORIGINAL_ID, id);
1960222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        mDb.update(Tables.EVENTS, originalValues,
1961222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                Events.ORIGINAL_SYNC_ID + "=? AND " + Events.CALENDAR_ID + "=?",
1962222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                new String[] { syncId, calendarId });
1963222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    }
1964222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
19659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
1966b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
1967ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
19689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
19699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
19700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
19710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
19720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                null /* selection */, null /* selection args */);
19739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
19759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
1977bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case SYNCSTATE:
19789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
19799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
19809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
19817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
1982c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
19837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
19849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
19859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
19869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
19879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
1988e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
1989be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
1990be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(updatedValues, null);
1991be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
1992be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(updatedValues);
1993be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
1994e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
1995e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
19969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
19979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
19989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
19999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
20019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
20029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
20039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
20049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
20059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
20069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
20079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
20089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
20099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
20109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
20119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
201234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
201334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
201434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    long originalId = getOriginalId(updatedValues
201534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsString(Events.ORIGINAL_SYNC_ID));
201634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (originalId != -1) {
201734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_ID, originalId);
201834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
201934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
202034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && updatedValues.containsKey(Events.ORIGINAL_ID)) {
202134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    String originalSyncId = getOriginalSyncId(updatedValues
202234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsLong(Events.ORIGINAL_ID));
202334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (!TextUtils.isEmpty(originalSyncId)) {
202434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
202534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
202634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                }
2027d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (fixAllDayTime(updatedValues, updatedValues)) {
2028f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2029f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
2030f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
2031f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
2032646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
2033c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
20349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
20359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
20369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
2037f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                    mInstancesHelper.updateInstancesLocked(updatedValues, id,
2038f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                            true /* new event */, mDb);
20399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
20419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
20429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
20439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
20449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
20498ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // if the Event Timezone is defined, store it as the original one in the
20508ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // ExtendedProperties table
20518ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    if (values.containsKey(Events.EVENT_TIMEZONE) && !callerIsSyncAdapter) {
20528ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        String originalTimezone = values.getAsString(Events.EVENT_TIMEZONE);
20538ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20548ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        ContentValues expropsValues = new ContentValues();
2055b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.EVENT_ID, id);
2056b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.NAME,
20578ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                EXT_PROP_ORIGINAL_TIMEZONE);
2058b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.VALUE,
2059b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                originalTimezone);
20608ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
20618ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        // Insert the extended property
20628ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        long exPropId = mDbHelper.extendedPropertiesInsert(expropsValues);
20638ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        if (exPropId == -1) {
20648ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
20658ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                Log.e(TAG, "Cannot add the original Timezone in the "
20668ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                        + "ExtendedProperties table for Event: " + id);
20678ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
20688ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        } else {
20698ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            // Update the Event for saying it has some extended properties
20708ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            ContentValues eventValues = new ContentValues();
20718ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            eventValues.put(Events.HAS_EXTENDED_PROPERTIES, "1");
2072b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            int result = mDb.update("Events", eventValues, SQL_WHERE_ID,
20738ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    new String[] {String.valueOf(id)});
20748ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (result <= 0) {
20758ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                if (Log.isLoggable(TAG, Log.ERROR)) {
20768ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    Log.e(TAG, "Cannot update hasExtendedProperties column"
20778ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                            + " for Event: " + id);
20788ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                }
20798ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
20808ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        }
20818ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    }
2082b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
2083222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                    backfillExceptionOriginalIds(id, values);
2084222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2085dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2088bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID:
2089bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long originalEventId = ContentUris.parseId(uri);
2090c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                id = handleInsertException(originalEventId, values, callerIsSyncAdapter);
2091bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                break;
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
2095c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
2097c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                            Calendars.ACCOUNT_TYPE);
20989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
2099fa332ecedc0c340109811552407142f6e4f600b2RoboErik                    String eventsUrl = values.getAsString(Calendars.CAL_SYNC1);
21001b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2103dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
21049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21119ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Attendees.EVENT_ID);
21129ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
21139ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
21147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.attendeesInsert(values);
21169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
21199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
21219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
21229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
21239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
21269ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Reminders.EVENT_ID);
21279ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
21289ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
21297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21309ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.remindersInsert(values);
21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
21339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
21349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2136420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
21379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
21442fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
21452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
21469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
2148b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                if (!values.containsKey(CalendarContract.ExtendedProperties.EVENT_ID)) {
21499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
21509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
21519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2153b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    final Long eventId = values
2154b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            .getAsLong(CalendarContract.ExtendedProperties.EVENT_ID);
21559ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
21569ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
21577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21589ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.extendedPropertiesInsert(values);
21599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21603b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            case EMMA:
21613b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                // Special target used during code-coverage evaluation.
21623b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                handleEmmaRequest(values);
21633b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                break;
21649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
21659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
21669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
21679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
21706db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2171315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
21727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
21749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2184e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
21853b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * Handles special commands related to EMMA code-coverage testing.
21863b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     *
21873b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * @param values Parameters from the caller.
21883b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     */
21893b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static void handleEmmaRequest(ContentValues values) {
21903b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        /*
21913b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * This is not part of the public API, so we can't share constants with the CTS
21923b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * test code.
21933b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         *
21943b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * Bad requests, or attempting to request EMMA coverage data when the coverage libs
21953b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * aren't linked in, will cause an exception.
21963b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         */
21973b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        String cmd = values.getAsString("cmd");
21983b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        if (cmd.equals("start")) {
21993b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // We'd like to reset the coverage data, but according to FAQ item 3.14 at
22003b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // http://emma.sourceforge.net/faq.html, this isn't possible in 2.0.
22013b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            Log.d(TAG, "Emma coverage testing started");
22023b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        } else if (cmd.equals("stop")) {
22033b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // Call com.vladium.emma.rt.RT.dumpCoverageData() to cause a data dump.  We
22043b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // may not have been built with EMMA, so we need to do this through reflection.
22053b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            String filename = values.getAsString("outputFileName");
22063b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
22073b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            File coverageFile = new File(filename);
22083b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            try {
22093b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
22103b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
22113b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        coverageFile.getClass(), boolean.class, boolean.class);
22123b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
22133b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                dumpCoverageMethod.invoke(null, coverageFile, false /*merge*/,
22143b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        false /*stopDataCollection*/);
22153b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Log.d(TAG, "Emma coverage data written to " + filename);
22163b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            } catch (Exception e) {
22173b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                throw new RuntimeException("Emma coverage dump failed", e);
22183b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            }
22193b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        }
22203b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    }
22213b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
22223b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    /**
22235ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * Validates the recurrence rule, if any.  We allow single- and multi-rule RRULEs.
2224ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * <p>
22255ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * TODO: Validate RDATE, EXRULE, EXDATE (possibly passing in an indication of whether we
22265ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * believe we have the full set, so we can reject EXRULE when not accompanied by RRULE).
2227ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     *
2228ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * @return A boolean indicating successful validation.
2229ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     */
2230ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    private boolean validateRecurrenceRule(ContentValues values) {
2231ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2232ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2233ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        if (!TextUtils.isEmpty(rrule)) {
22345ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            String[] ruleList = rrule.split("\n");
22355ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            for (String recur : ruleList) {
22365ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                EventRecurrence er = new EventRecurrence();
22375ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                try {
22385ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    er.parse(recur);
22395ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                } catch (EventRecurrence.InvalidFormatException ife) {
22405ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    Log.w(TAG, "Invalid recurrence rule: " + recur);
22415ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    return false;
22425ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                }
2243ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2244ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        }
2245ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2246ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        return true;
2247ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    }
2248ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2249ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    /**
225062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Do some scrubbing on event data before inserting or updating. In particular make
225162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * dtend, duration, etc make sense for the type of event (regular, recurrence, exception).
225262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Remove any unexpected fields.
2253e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
225462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param values the ContentValues to insert.
225562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param modValues if non-null, explicit null entries will be added here whenever something
225662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     *   is removed from <strong>values</strong>.
2257e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
225862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden    private void scrubEventData(ContentValues values, ContentValues modValues) {
2259e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2260e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2261e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2262e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2263c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_SYNC_ID));
2264e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2265e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2266e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2267e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2268e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2269e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2270ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            // rrule is a valid recurrence rule
2271e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2272e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2273e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2274ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            if (!validateRecurrenceRule(values)) {
2275ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2276ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        values.getAsString(Events.RRULE));
2277ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2278e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
227962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DTEND, ORIGINAL_SYNC_ID, ORIGINAL_INSTANCE_TIME");
2280e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
228162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence: " + values);
2282e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2283e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2284c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                values.remove(Events.ORIGINAL_SYNC_ID);
2285e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
228662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
228762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DTEND);
228862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_SYNC_ID);
228962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_INSTANCE_TIME);
229062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2291e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2292e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2293e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2294e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2295e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2296e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2297e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2298e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2299e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2300e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2301e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
230262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2303e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
230462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence exception: " + values);
2305e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2306e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
230762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
230862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
230962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2310e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2311e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2312e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2313e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2314e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2315e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2316e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2317e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2318e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2319e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2320e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
232162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2322e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
232362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for event: " + values);
2324e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2325e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
232662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
232762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
232862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2329e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2330e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2331e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2332e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
2333d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2334d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Validates event data.  Pass in the full set of values for the event (i.e. not just
2335d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * a part that's being updated).
2336d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2337d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values Event data.
2338d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws IllegalArgumentException if bad data is found.
2339d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
2340d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private void validateEventData(ContentValues values) {
2341d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtstart = values.getAsLong(Events.DTSTART) != null;
2342d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2343d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2344d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2345d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2346d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasCalId = !TextUtils.isEmpty(values.getAsString(Events.CALENDAR_ID));
2347d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasCalId) {
2348d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("New events must include a calendar_id.");
2349d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2350d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasRrule || hasRdate) {
2351d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!validateRecurrenceRule(values)) {
2352d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2353d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        values.getAsString(Events.RRULE));
2354d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
2355d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2356d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
2357d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDtstart) {
2358d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTSTART cannot be empty.");
2359d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2360d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDuration && !hasDtend) {
2361d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
2362d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    "an event.");
2363d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2364d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasDuration && hasDtend) {
2365d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("Cannot have both DTEND and DURATION in an event");
2366d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2367d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
2368d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
23699ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private void setEventDirty(long eventId) {
23709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Object[] {eventId});
23717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
23727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
237334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private long getOriginalId(String originalSyncId) {
237434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (TextUtils.isEmpty(originalSyncId)) {
237534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return -1;
237634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
237734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
237834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        long originalId = -1;
237934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
238034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
238134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
238234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._SYNC_ID + "=?", new String[] {originalSyncId}, null);
238334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
238434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalId = c.getLong(0);
238534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
238634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
238734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
238834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
238934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
239034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
239134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalId;
239234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
239334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
239434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private String getOriginalSyncId(long originalId) {
239534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (originalId == -1) {
239634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return null;
239734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
239834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
239934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        String originalSyncId = null;
240034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
240134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
240234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
240334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
240434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
240534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalSyncId = c.getString(0);
240634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
240734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
240834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
240934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
241034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
241134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
241234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalSyncId;
241334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
241434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2421f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2422f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2423f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2424f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2425f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2426f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2437f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2438f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2439f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
24409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
24419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
24439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
24449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
24469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
24539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
24549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
24599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
24619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
24639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
24659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
24669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
24679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
24739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
24749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
24839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
24899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
24919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
24939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2499f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2500f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2501f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
25029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
25039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
25179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
25189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
25199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
25209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2521f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2522f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2523f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
25249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
25259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
25289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
25349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
25359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
25389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
25399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
25419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
25449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
25459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
25469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
25479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
25539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
25549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
25619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
25649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2565b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2566b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
25679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2569d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2570d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Calculates the "last date" of the event.  For a regular event this is the start time
2571d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * plus the duration.  For a recurring event this is the start date of the last event in
2572d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence, plus the duration.  The event recurs forever, this returns -1.  If
2573d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence rule can't be parsed, this returns -1.
2574d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2575d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values
2576d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the date, in milliseconds, since the start of the epoch (UTC), or -1 if an
2577d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   exceptional condition exists.
2578d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws DateException
2579d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
25809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
25839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
25849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
25859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
25869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
25879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
25899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
25909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
25919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
25929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
25949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
25969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
25979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
26029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
26059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
26069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
26079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
26089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
26099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
26109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
26119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
26129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2614f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2615f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2616f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2617f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2618f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2619f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2620b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            values.get(CalendarContract.Events.RRULE), e);
2621f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2622d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: this should throw an exception or return a distinct error code
2623f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2624f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
26259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2626f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
26279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
26289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
26299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
26379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
2643d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // repeats forever
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
26459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
26529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
26539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2657e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2658e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
2659e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues (in/out)
2660e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2661e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2662e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
26649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
26659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
26669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
26679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2672f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2673f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2674f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2679d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2680d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Creates or updates an entry in the EventsRawTimes table.
2681d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2682d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param eventId The ID of the event that was just created or is being updated.
2683d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values For a new event, the full set of event values; for an updated event,
2684d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   the set of values that are being changed.
2685d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2689b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        rawValues.put(CalendarContract.EventsRawTimes.EVENT_ID, eventId);
26909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
26929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
27019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
27029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
27059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
27069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
2709b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTSTART_2445, time.format2445());
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
2715b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTEND_2445, time.format2445());
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
2728b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445,
2729b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
27339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
27349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
27359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
2736b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.LAST_DATE_2445, time.format2445());
27379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2743b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2744b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2745ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
27469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
27479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
27490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
27500739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
27510739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
27529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
27539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
27572ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
27589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
27599323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2760dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2761dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2762dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2763dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
27669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
27677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
27680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection = appendSyncAccountToSelection(uri, selection);
27697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
27701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
2771ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak                Cursor cursor = mDb.query(Views.EVENTS, ID_ONLY_PROJECTION,
27721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
27737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
27751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
27761ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
277710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
27789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2779420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
2780dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
27819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
27829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
27849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
27881ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
27891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
27901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
27911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
27921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
27931ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
27941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
279510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
27961ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
2797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID2:
2798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            {
2799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // This will throw NumberFormatException on missing or malformed input.
2800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                List<String> segments = uri.getPathSegments();
2801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long eventId = Long.parseLong(segments.get(1));
2802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long excepId = Long.parseLong(segments.get(2));
2803bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // TODO: verify that this is an exception instance (has an ORIGINAL_ID field
2804bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //       that matches the supplied eventId)
2805bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return deleteEventInternal(excepId, callerIsSyncAdapter, false /* isBatch */);
2806bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
28079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
28089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2810b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
28117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2812b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, selection, selectionArgs);
28137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
28169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28172fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28182fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2822b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
2823b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
28247e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2825b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, null /* selection */,
28262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2832b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, selection, selectionArgs);
28337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2834b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, selection, selectionArgs);
28357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
28437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2844b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, SQL_WHERE_ID,
2845b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
28467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2847b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, null /* selection */,
28482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28502fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
28512fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
28522fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
28532fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
2854b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
28552fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2856b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, selection,
2857b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
28582fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
28602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
28612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
28622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28632fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28642fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28652fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
28662fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
2867b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
2868636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
28692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2870b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, null /* selection */,
28712fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
28727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
28759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2877b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
28787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2879b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.CALENDAR_ALERTS, uri, selection, selectionArgs);
28807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
28819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
28839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
28842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
28852fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
28862fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
28872fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
28882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
28899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2890b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
2891b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
28929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
28942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                StringBuilder selectionSb = new StringBuilder(Calendars._ID + "=");
28959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
28969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
28989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
28999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
29009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
29039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
2904595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
290574ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                return deleteMatchingCalendars(selection, selectionArgs);
29069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
29079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
29086db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2909315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
29119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
29139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
291610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
29171ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
2918192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
29191ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29201ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
2921b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
2922b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
2923636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
29241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
29251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
29261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
29271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
29281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
292948f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
29301ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
29311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
29321ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
29331ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
29341ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
29351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
2936b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origId = cursor.getString(EVENTS_ORIGINAL_ID_INDEX);
2937b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origSyncId = cursor.getString(EVENTS_ORIGINAL_SYNC_ID_INDEX);
2938b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                if (isRecurrenceEvent(rrule, rdate, origId, origSyncId)) {
29391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
29401ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
29414d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                boolean isRecurrence = !TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate);
29421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
294348f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
294448f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
2945bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //
2946bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // The EVENTS_CLEANUP_TRIGGER_SQL trigger will remove all associated data
2947bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // (Attendees, Instances, Reminders, etc).
294848f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
2949b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
29504d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
29514d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // If this is a recurrence, and the event was never synced with the server,
29524d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we want to delete any exceptions as well.  (If it has been to the server,
29534d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we'll let the sync adapter delete the events explicitly.)  We assume that,
29544d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // if the recurrence hasn't been synced, the exceptions haven't either.
29554d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    if (isRecurrence && emptySyncId) {
29564d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                        mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID, selectionArgs);
29574d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    }
29581ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
2959bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // Event is on the server, so we "soft delete", i.e. mark as deleted so that
2960bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the sync adapter has a chance to tell the server about the deletion.  After
2961bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the server sees the change, the sync adapter will do the "hard delete"
2962bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // (above).
29631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
29641b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
2965c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
2966b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
296702494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
29684d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // Exceptions that have been synced shouldn't be deleted -- the sync
29694d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // adapter will take care of that -- but we want to "soft delete" them so
29704d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // that they will be removed from the instances list.
29714d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // TODO: this seems to confuse the sync adapter, and leaves you with an
29724d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       invisible "ghost" event after the server sync.  Maybe we can fix
29734d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       this by making instance generation smarter?  Not vital, since the
29744d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       exception instances disappear after the server sync.
29754d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //mDb.update(Tables.EVENTS, values, SQL_WHERE_ORIGINAL_ID_HAS_SYNC_ID,
29764d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //        selectionArgs);
29774d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
29784d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // It's possible for the original event to be on the server but have
29794d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // exceptions that aren't.  We want to remove all events with a matching
29804d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // original_id and an empty _sync_id.
29814d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID,
29824d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                            selectionArgs);
29834d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
298443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
298543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
2986b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
2987b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
2988b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
2989b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
2990b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
2991b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
29921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
29931ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
29941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
29951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
29961ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
29971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
29988f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
299910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
3000420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3001dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
300210b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
30031ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
30041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
30051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
30067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
30077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
30087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
30097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
30107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
30117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
30127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
30137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
30147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
30157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
30169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
30179ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final ContentValues values = new ContentValues();
3018c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        values.put(Events.DIRTY, "1");
30197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
30207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
30217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
30229ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long id = c.getLong(ID_INDEX);
30239ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long event_id = c.getLong(EVENT_ID_INDEX);
30249ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDbHelper.duplicateEvent(event_id);
30259ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDb.delete(table, SQL_WHERE_ID, new String[]{String.valueOf(id)});
3026b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
3027b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
30287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
30297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
30307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
30317e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
30327e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
30337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
30347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
30357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
30367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
30377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
30387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
30397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
30407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
30417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
30427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
30437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
30447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
30457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
30467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
30477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
30489ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
30499ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final ContentValues dirtyValues = new ContentValues();
3050c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        dirtyValues.put(Events.DIRTY, "1");
30517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
30527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
30537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
30549ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long id = c.getLong(ID_INDEX);
30559ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long event_id = c.getLong(EVENT_ID_INDEX);
30569ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDbHelper.duplicateEvent(event_id);
3057b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(table, values, SQL_WHERE_ID, new String[] {String.valueOf(id)});
3058b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
3059b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
30607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
30617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
30627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
30637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
30647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
30657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
30667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
30677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
306874ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio    private int deleteMatchingCalendars(String selection, String[] selectionArgs) {
30699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
30709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
30719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
307274ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, selection,
307374ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                selectionArgs,
307474ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* groupBy */,
307574ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* having */,
307674ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* sortOrder */);
30779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
30789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
30799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
30819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
30829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
30839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
30849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
30869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
30879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
308874ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        return mDb.delete(Tables.CALENDARS, selection, selectionArgs);
30899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3091fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
3092fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
3093fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3094fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
3095fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3096fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
3097fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3098fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
3099fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
3100fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
3101fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3102fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3103fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
3104fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
3105fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3106fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
3107fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
3108fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
3109fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3110fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
3111fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
3112fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
3113d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean doesStatusCancelUpdateMeanUpdate(ContentValues values,
3114d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues modValues) {
3115d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean isStatusCanceled = modValues.containsKey(Events.STATUS) &&
3116d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                (modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
3117fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
3118d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID);
3119d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3120d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!TextUtils.isEmpty(originalSyncId)) {
3121d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // This event is an exception.  See if the recurring event still exists.
3122d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                return doesEventExistForSyncId(originalSyncId);
3123d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3124d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3125d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // This is the normal case, we just want an UPDATE
3126d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return true;
3127d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
3128d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3129d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3130d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
3131d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Handles a request to update one or more events.
3132d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
3133d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * The original event(s) will be loaded from the database, merged with the new values,
3134d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * and the result checked for validity.  In some cases this will alter the supplied
3135d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * arguments (e.g. zeroing out the times on all-day events), change additional fields (e.g.
3136d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * update LAST_DATE when DTSTART changes), or cause modifications to other tables (e.g. reset
3137d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Instances when a recurrence rule changes).
3138d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
3139d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param cursor The set of events to update.
31404b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden     * @param updateValues The changes to apply to each event.
3141d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param callerIsSyncAdapter Indicates if the request comes from the sync adapter.
3142d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the number of rows updated
3143d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
31444b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden    private int handleUpdateEvents(Cursor cursor, ContentValues updateValues,
3145d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean callerIsSyncAdapter) {
3146d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        /*
3147d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For a single event, we can just load the event, merge modValues in, perform any
3148d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * fix-ups (putting changes into modValues), check validity, and then update().  We have
3149d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * to be careful that our fix-ups don't confuse the sync adapter.
3150d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         *
3151d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For multiple events, we need to load, merge, and validate each event individually.
3152d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * If no single-event-specific changes need to be made, we could just issue the original
3153d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * bulk update, which would be more efficient than a series of individual updates.
3154d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * However, doing so would prevent us from taking advantage of the partial-update
3155d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * mechanism.
3156d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         */
3157d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (cursor.getCount() > 1) {
3158d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (Log.isLoggable(TAG, Log.DEBUG)) {
3159d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG, "Performing update on " + cursor.getCount() + " events");
3160d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3161d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3162d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        while (cursor.moveToNext()) {
31639f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Make a copy of updateValues so we can make some local changes.
31644b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden            ContentValues modValues = new ContentValues(updateValues);
31659f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
31669f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Load the event into a ContentValues object.
3167d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues values = new ContentValues();
3168d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
31699f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            boolean doValidate = false;
31709f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (!callerIsSyncAdapter) {
31719f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                try {
31729f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // Check to see if the data in the database is valid.  If not, we will skip
31739f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // validation of the update, so that we don't blow up on attempts to
31749f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // modify existing badly-formed events.
31759f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    validateEventData(values);
31769f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    doValidate = true;
31779f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                } catch (IllegalArgumentException iae) {
31789f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    Log.d(TAG, "Event " + values.getAsString(Events._ID) +
31799f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            " malformed, not validating update (" +
31809f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            iae.getMessage() + ")");
31819f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                }
31829f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
31839f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
31849f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Merge the modifications in.
3185d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            values.putAll(modValues);
3186d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
31879f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Scrub and/or validate the combined event.
3188be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            if (callerIsSyncAdapter) {
3189be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                scrubEventData(values, modValues);
31909f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
31919f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (doValidate) {
3192be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                validateEventData(values);
3193be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            }
3194d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3195d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Look for any updates that could affect LAST_DATE.  It's defined as the end of
3196d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // the last meeting, so we need to pay attention to DURATION.
3197d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.DTSTART) ||
3198d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DTEND) ||
3199d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DURATION) ||
3200d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EVENT_TIMEZONE) ||
3201d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RRULE) ||
3202d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RDATE) ||
3203d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXRULE) ||
3204d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXDATE)) {
3205d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long newLastDate;
3206d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3207d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    newLastDate = calculateLastDate(values);
3208d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } catch (DateException de) {
3209d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    throw new IllegalArgumentException("Unable to compute LAST_DATE", de);
3210d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3211d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Long oldLastDateObj = values.getAsLong(Events.LAST_DATE);
3212d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long oldLastDate = (oldLastDateObj == null) ? -1 : oldLastDateObj;
3213d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (oldLastDate != newLastDate) {
3214d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // This overwrites any caller-supplied LAST_DATE.  This is okay, because the
3215d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // caller isn't supposed to be messing with the LAST_DATE field.
3216d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (newLastDate < 0) {
3217d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.putNull(Events.LAST_DATE);
3218d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    } else {
3219d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.put(Events.LAST_DATE, newLastDate);
3220fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3221fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3222d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3223d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3224d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!callerIsSyncAdapter) {
3225d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DIRTY, 1);
3226d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3227fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3228d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Disallow updating the attendee status in the Events
3229d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // table.  In the future, we could support this but we
3230d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // would have to query and update the attendees table
3231d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // to keep the values consistent.
3232d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
3233d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Updating "
3234d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + Events.SELF_ATTENDEE_STATUS
3235d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + " in Events table is not allowed.");
3236d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3237d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3238d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (fixAllDayTime(values, modValues)) {
3239d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (Log.isLoggable(TAG, Log.WARN)) {
3240d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    Log.w(TAG, "handleUpdateEvents: " +
3241d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            "allDay is true but sec, min, hour were not 0.");
3242fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3243d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3244d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3245d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // For taking care about recurrences exceptions cancelations, check if this needs
3246d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            //  to be an UPDATE or a DELETE
3247d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean isUpdate = doesStatusCancelUpdateMeanUpdate(values, modValues);
3248d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3249d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            long id = values.getAsLong(Events._ID);
3250d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3251d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (isUpdate) {
3252d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // If a user made a change, possibly duplicate the event so we can do a partial
3253d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // update. If a sync adapter made a change and that change marks an event as
3254d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // un-dirty, remove any duplicates that may have been created earlier.
3255d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (!callerIsSyncAdapter) {
3256d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mDbHelper.duplicateEvent(id);
3257d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } else {
3258d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DIRTY)
3259d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            && modValues.getAsInteger(Events.DIRTY) == 0) {
3260d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mDbHelper.removeDuplicateEvent(id);
3261d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3262d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3263d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int result = mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
3264d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        new String[] { String.valueOf(id) });
3265d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (result > 0) {
3266d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    updateEventRawTimesLocked(id, modValues);
3267d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mInstancesHelper.updateInstancesLocked(modValues, id,
3268d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            false /* not a new event */, mDb);
3269d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3270d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // XXX: should we also be doing this when RRULE changes (e.g. instances
3271d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    //      are introduced or removed?)
3272d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DTSTART) ||
3273d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            modValues.containsKey(Events.STATUS)) {
3274d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // If this is a cancellation knock it out
3275d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // of the instances table
3276d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (modValues.containsKey(Events.STATUS) &&
3277d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED) {
3278d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            String[] args = new String[] {String.valueOf(id)};
3279d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, args);
3280d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3281d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3282d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // The start time or status of the event changed, so run the
3283d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // event alarm scheduler.
3284d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (Log.isLoggable(TAG, Log.DEBUG)) {
3285d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            Log.d(TAG, "updateInternal() changing event");
3286d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3287d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3288d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3289d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3290d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    sendUpdateNotification(id, callerIsSyncAdapter);
3291d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3292d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else {
3293d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3294d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3295d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                sendUpdateNotification(callerIsSyncAdapter);
3296fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3297fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3298d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3299d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return cursor.getCount();
3300fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3301fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
33029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
33039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
3304b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
3305ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
33069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
33079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
33090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
33100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
33119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
33139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
331543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS
3316315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                && match != EVENTS && match != CALENDARS && match != PROVIDER_PROPERTIES) {
3317b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            throw new IllegalArgumentException("WHERE based updates not supported");
33189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
3319fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
33209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
33219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
33229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
33239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
33249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
33269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
33272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
3328dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
33299323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3330dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3331dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3332dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
33339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
333543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
33369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
33379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
333843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
333943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
334043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null) {
334143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        throw new UnsupportedOperationException("Selection not permitted for "
334243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                                + uri);
334343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
334443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
334543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
334643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
334743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
334843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
334943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
335043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
33514cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    if (selection != null && TextUtils.equals(selection,"_id=?")) {
33524cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                        id = Long.parseLong(selectionArgs[0]);
33534cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
335443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
335543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
335643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
335743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
3358b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
335943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
336043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
336143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
3362c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Calendars.DIRTY, 1);
33632fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
33649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
33659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
33669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
33679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
33689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3369b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
3370636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
33719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33723ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
3373d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
33744067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                    if (values.containsKey(Calendars.VISIBLE)) {
3375d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
3376d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
3377d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
3378d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
3379420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        mCalendarAlarm.scheduleNextAlarm(false);
3380d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
33813ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
3382dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
33833ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
33843ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
33859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
33869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
33877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
33889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
33899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
3390d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Cursor events = null;
33919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3392d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // Grab the full set of columns for each selected event.
3393d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: define a projection with just the data we need (e.g. we don't need to
3394d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                //       validate the SYNC_* columns)
33959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3396d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3397d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (match == EVENTS_ID) {
3398d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // Single event, identified by ID.
3399d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        long id = ContentUris.parseId(uri);
3400d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3401d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                SQL_WHERE_ID, new String[] { String.valueOf(id) },
3402d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
34039ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    } else {
3404d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // One or more events, identified by the selection / selectionArgs.
3405d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (!callerIsSyncAdapter) {
3406d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            // Only the sync adapter can use this URI.
3407d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            throw new IllegalArgumentException("Invalid URI: " + uri);
34089ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                        }
3409d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3410d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3411d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                selection, selectionArgs,
3412d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
34139ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    }
341406c305d35741db303bd3aacd0eab5af8de0ab34eErik
3415d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events.getCount() == 0) {
3416d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        Log.w(TAG, "No events to update: uri=" + uri + " selection=" + selection +
3417d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                " selectionArgs=" + Arrays.toString(selectionArgs));
3418d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        return 0;
3419d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
34203ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3421d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    return handleUpdateEvents(events, values, callerIsSyncAdapter);
3422d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } finally {
3423d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events != null) {
3424d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events.close();
3425fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
34269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
34279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34282fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
34292fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
34302fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
34312fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
34329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
34339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
34349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
34367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3437b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.ATTENDEES, values, SQL_WHERE_ID,
343883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
34397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3440b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.ATTENDEES, values, uri, null /* selection */,
34412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
34427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
34439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34442fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
34452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
34462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
34472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
34482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
34492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
34509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3451b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
3452636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
34539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34542fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
34552fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
34562fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
3457b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
34589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
34602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
34612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
34622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
34637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
34647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3465b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = mDb.update(Tables.REMINDERS, values, SQL_WHERE_ID,
346683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
34677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3468b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = updateInTable(Tables.REMINDERS, values, uri, null /* selection */,
34692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
34707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
34717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
34729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
34739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
34749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
34759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
34769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3477420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
34787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
34799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
34812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
34822fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
34832fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
34847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
34857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3486b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.EXTENDED_PROPERTIES, values, SQL_WHERE_ID,
3487636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
34887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3489b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.EXTENDED_PROPERTIES, values, uri,
3490b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            null /* selection */, null /* selectionArgs */);
34917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
34929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
349383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
349483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
349583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
3496420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false);
349783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
349883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
349983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
3500420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(true);
350183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
350283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
35039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3504315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
3505315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (selection == null) {
3506315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection cannot be null for " + uri);
3507315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3508315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
3509315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
3510315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3511315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3512315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
3513315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3514315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
3515315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
3516315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
3517315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3518315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3519315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
3520315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
3521315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3522315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
3523315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
3524b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
3525315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3526315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
3527f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "home", set the Instances
3528f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the previous
3529f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "auto", set the Instances
3530f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the current
3531f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // device one
3532f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone Instances is set AND if we are in "home"
3533f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone type, then save the timezone Instance into
3534f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // "previous" too
3535315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
3536315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
3537315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
3538315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
3539315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
3540315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
3541315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
3542315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
3543315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
3544315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
3545315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
3546315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3547315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
3548d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
3549315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
3550315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3551d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3552315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3553315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3554315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
3555315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
3556315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
3557315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
3558315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
3559315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3560d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3561315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3562315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3563315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3564315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3565315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
3566315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
3567315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
3568315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
3569315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
3570315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
3571315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
3572315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
3573d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
3574315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
3575315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
3576315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
3577d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
3578315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3579315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3580315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3581315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3582315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
3583315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
3584315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
35859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
35869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
35879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendAccountFromParameterToSelection(String selection, Uri uri) {
3591b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3592b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
3593b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3594b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
3595595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
35969ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            final StringBuilder sb = new StringBuilder();
35979ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(Calendars.ACCOUNT_NAME + "=")
35989ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountName))
35999ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" AND ")
36009ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(Calendars.ACCOUNT_TYPE)
36019ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" = ")
36029ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountType));
36039ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(sb, selection);
3604595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
36059ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
36069ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
36079ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
36089ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
36099ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendLastSyncedColumnToSelection(String selection, Uri uri) {
36109ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (getIsCallerSyncAdapter(uri)) {
36119ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
3612595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
36139ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final StringBuilder sb = new StringBuilder();
3614b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sb.append(CalendarContract.Events.LAST_SYNCED).append(" = 0");
36159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return appendSelection(sb, selection);
3616595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3617595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
36189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
3619b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3620b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
3621b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3622b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
36239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
3624b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Calendars.ACCOUNT_NAME
3625b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + "=" + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3626b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Calendars.ACCOUNT_TYPE + "="
36270739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountType));
36289ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
36290739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        } else {
36300739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return selection;
36310739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
36320739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
36330739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
36340739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private String appendSyncAccountToSelection(Uri uri, String selection) {
36350739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3636b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
36370739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3638b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
36390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (!TextUtils.isEmpty(accountName)) {
3640b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Events.ACCOUNT_NAME + "="
36410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3642b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Events.ACCOUNT_TYPE + "="
36439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
36449ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
36459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
36469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
36479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
36499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36509ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendSelection(StringBuilder sb, String selection) {
36519ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (!TextUtils.isEmpty(selection)) {
36529ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(" AND (");
36539ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(selection);
36549ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(')');
36559ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
36569ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return sb.toString();
36579ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
36589ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
36590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    /**
36600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * Verifies that the operation is allowed and throws an exception if it
36610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * isn't. This defines the limits of a sync adapter call vs an app call.
3662c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik     *
36630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param type The type of call, {@link #TRANSACTION_QUERY},
36640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_INSERT}, {@link #TRANSACTION_UPDATE}, or
36650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_DELETE}
36660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param uri
36670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param values
36680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param isSyncAdapter
36690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     */
36700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyTransactionAllowed(int type, Uri uri, ContentValues values,
36710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            boolean isSyncAdapter, int uriMatch, String selection, String[] selectionArgs) {
36720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (type) {
36730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_QUERY:
36740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
36750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_INSERT:
36760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
36770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException(
36780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                            "Inserting into instances not supported");
36790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
3680c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
3681c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
36820739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
36830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
36840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
36850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
36860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
36870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
36880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
36890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
36900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_UPDATE:
36910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
36920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Updating instances not supported");
36930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
3694c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
3695c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
36960739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
36970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
36980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
36990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
37000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
37010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
37020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
37030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
37040739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_DELETE:
37050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
37060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Deleting instances not supported");
37070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
37080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
37090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
37100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
37110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
37120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
37130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37140739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
37150739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
37160739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyHasAccount(Uri uri, String selection, String[] selectionArgs) {
3717c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String accountName = QueryParameterUtils.getQueryParameter(uri, Calendars.ACCOUNT_NAME);
37180739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String accountType = QueryParameterUtils.getQueryParameter(uri,
3719c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Calendars.ACCOUNT_TYPE);
37200739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
37210739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (selection != null && selection.startsWith(ACCOUNT_SELECTION_PREFIX)) {
37220739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountName = selectionArgs[0];
37230739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountType = selectionArgs[1];
37240739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
37250739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37260739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
37270739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            throw new IllegalArgumentException(
37280739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    "Sync adapters must specify an account and account type: " + uri);
37290739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37300739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
37310739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
3732c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private void verifyColumns(ContentValues values, int uriMatch) {
3733c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
3734c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            return;
3735c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3736c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String[] columns;
3737c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        switch (uriMatch) {
3738c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
3739c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
3740c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
3741c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
3742c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = Events.PROVIDER_WRITABLE_COLUMNS;
3743c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3744c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            default:
3745c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = PROVIDER_WRITABLE_DEFAULT_COLUMNS;
3746c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3747c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3748c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
3749c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        for (int i = 0; i < columns.length; i++) {
3750c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            if (values.containsKey(columns[i])) {
3751c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                throw new IllegalArgumentException("Only the provider may write to " + columns[i]);
3752c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            }
3753c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3754c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    }
3755c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
37560739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyNoSyncColumns(ContentValues values, int uriMatch) {
3757c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
37580739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return;
37590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String[] syncColumns;
37610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (uriMatch) {
37620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS:
37630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS_ID:
37640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES:
37650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES_ID:
3766c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Calendars.SYNC_WRITABLE_COLUMNS;
3767c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3768c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
3769c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
3770c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
3771c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
3772c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Events.SYNC_WRITABLE_COLUMNS;
37730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
37740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            default:
37750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                syncColumns = SYNC_WRITABLE_DEFAULT_COLUMNS;
37760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
37770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
37780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        for (int i = 0; i < syncColumns.length; i++) {
37800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (values.containsKey(syncColumns[i])) {
37810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                throw new IllegalArgumentException("Only sync adapters may write to "
37820739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                        + syncColumns[i]);
37830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
37840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
37850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
37860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
37879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
37889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
37899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
37909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
3791c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                new String[] {Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
3792fa332ecedc0c340109811552407142f6e4f600b2RoboErik                        Calendars.CAL_SYNC1, Calendars.SYNC_EVENTS},
37939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
37949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
37959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
37969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
37979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
37989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
37999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
3800ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
38019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3802ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
3803ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
3804ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
3805ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
3806ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
3807ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
3808ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
38099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
38109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
38119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38149535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
38159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
3816f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3817f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
3818f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
3819f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
38209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
38219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38239535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
38249535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
38259535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
38269535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
38279535627bf6295cd94447beb83e1aac41f50c3600Erik        }
38289535627bf6295cd94447beb83e1aac41f50c3600Erik
38299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
38309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
38319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
38329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
38359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
38369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
38379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
38389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3839a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3840a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3841a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3842a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
3843dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
38449ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
3845a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3846dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
3847dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
3848dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
3849a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3850a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3851a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3852a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3853a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3854a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
3855a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
3856a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
3857a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3858a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
3859a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
38609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param eventId the ID of the event that changed, or -1 for no specific event
38619ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
3862a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3863dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
3864dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
3865a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
3866a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
3867a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
3868a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
3869a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
3870dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
3871dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
3872dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
3873dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
3874dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
3875dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
3876dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
3877dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
3878dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
3879dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
3880dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
3881dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
3882dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
3883dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
3884dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
3885dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
3886dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
3887a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
3888dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
3889a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3890a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3891a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3892a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
3893a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
38949ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #sendUpdateNotification(boolean)} instead.
3895a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
38969ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @see #sendUpdateNotification(boolean)
3897a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3898a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
3899a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
3900b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.CONTENT_URI);
3901f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
3902f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
3903f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
3904e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
3905a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3906a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
39070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_QUERY = 0;
39080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_INSERT = 1;
39090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_UPDATE = 2;
39100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_DELETE = 3;
39110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
39120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:off
39130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final String[] SYNC_WRITABLE_DEFAULT_COLUMNS = new String[] {
3914b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars.DIRTY,
3915b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars._SYNC_ID
39160739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    };
3917c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String[] PROVIDER_WRITABLE_DEFAULT_COLUMNS = new String[] {
3918c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    };
39190739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:on
39200739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
39219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
39229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
39239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
39242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS = 4;
39252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS_ID = 5;
39262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES = 6;
39272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES_ID = 7;
39282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS = 8;
39292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS_ID = 9;
39302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES = 10;
39312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES_ID = 11;
39322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS = 12;
39332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_ID = 13;
39342ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_BY_INSTANCE = 14;
39352ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_BY_DAY = 15;
39362ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE = 16;
39372ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE_ID = 17;
39382ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES = 18;
39392ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES_ID = 19;
39402ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_DAYS = 20;
39412ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM = 21;
39422ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM_REMOVE = 22;
39432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int TIME = 23;
39442ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES = 24;
39452ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES_ID = 25;
39462ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH = 26;
39472ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH_BY_DAY = 27;
39482ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int PROVIDER_PROPERTIES = 28;
3949bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID = 29;
3950bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID2 = 30;
39513b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static final int EMMA = 31;
39529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
39549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
3955f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final HashMap<String, String> sEventsProjectionMap;
395619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
39579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
39589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
39599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
3960315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
396139c65e5716e21e863d8de587d139dae85f99422fFred Quintana    private static final HashMap<String, String> sCountProjectionMap;
39629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
3964b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/when/*/*", INSTANCES);
3965b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
3966b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
3967b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/searchbyday/*/*/*",
396881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
3969b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
3970b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events", EVENTS);
3971b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events/#", EVENTS_ID);
3972b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities", EVENT_ENTITIES);
3973b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
3974b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS);
3975b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS_ID);
3976b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
3977b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
3978b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees", ATTENDEES);
3979b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees/#", ATTENDEES_ID);
3980b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders", REMINDERS);
3981b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders/#", REMINDERS_ID);
3982b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
3983b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties/#",
3984b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                EXTENDED_PROPERTIES_ID);
3985b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
3986b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
3987b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/by_instance",
3988b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
3989b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate", SYNCSTATE);
3990b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
3991b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, CalendarAlarmManager.SCHEDULE_ALARM_PATH,
3992420b7fb569773ae573fbe90c3a9c522d4c368863Erik                SCHEDULE_ALARM);
3993b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY,
3994b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
3995b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time/#", TIME);
3996b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time", TIME);
3997b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "properties", PROVIDER_PROPERTIES);
3998b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#", EXCEPTION_ID);
3999b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#/#", EXCEPTION_ID2);
40003b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        sUriMatcher.addURI(CalendarContract.AUTHORITY, "emma", EMMA);
40019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
400239c65e5716e21e863d8de587d139dae85f99422fFred Quintana        /** Contains just BaseColumns._COUNT */
400339c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap = new HashMap<String, String>();
400439c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
400539c65e5716e21e863d8de587d139dae85f99422fFred Quintana
40069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
40079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
400802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_NAME, Events.ACCOUNT_NAME);
400902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_TYPE, Events.ACCOUNT_TYPE);
4010c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.TITLE, Events.TITLE);
4011c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4012c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4013c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.STATUS, Events.STATUS);
401402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4015c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4016c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTSTART, Events.DTSTART);
4017c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTEND, Events.DTEND);
4018c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4019c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4020c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DURATION, Events.DURATION);
4021c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4022c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4023c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4024c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4025c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, Events.HAS_EXTENDED_PROPERTIES);
4026c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RRULE, Events.RRULE);
4027c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RDATE, Events.RDATE);
4028c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
4029c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
4030c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
403134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4032c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
4033c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4034c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4035c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4036c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4037c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, Events.GUESTS_CAN_INVITE_OTHERS);
4038c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4039c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4040c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4041c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DELETED, Events.DELETED);
404202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
40439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4044e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
40451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
40461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
40471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
40489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
4049c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
405002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CALENDAR_ACCESS_LEVEL);
4051c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
405202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
4053c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
405402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
405502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
405602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
405702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND, Calendars.CAN_ORGANIZER_RESPOND);
405802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
40599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4060982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
4061e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
4062e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
4063982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4064e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4065982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
4066c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events._ID, Events._ID);
406702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
406802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
406902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
407002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
407102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
407202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
40739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
407402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
407502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
407602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
407702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
407802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
407902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
408002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
408102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
408202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
408302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
408402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
408502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
408602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
4087c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DIRTY, Events.DIRTY);
40889ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
40899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
409046f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
4091c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.TITLE, Events.TITLE);
4092c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4093c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4094c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.STATUS, Events.STATUS);
409502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4096c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4097c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTSTART, Events.DTSTART);
4098c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTEND, Events.DTEND);
4099c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4100c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4101c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DURATION, Events.DURATION);
4102c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4103c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4104c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4105c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4106c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES,
4107c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.HAS_EXTENDED_PROPERTIES);
4108c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RRULE, Events.RRULE);
4109c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RDATE, Events.RDATE);
4110c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
4111c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
4112c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
411334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4114c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
4115c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.ORIGINAL_INSTANCE_TIME);
4116c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4117c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4118c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4119c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4120c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS,
4121c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.GUESTS_CAN_INVITE_OTHERS);
4122c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4123c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4124c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4125c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DELETED, Events.DELETED);
412619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
412719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
412802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
412902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
413002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
413102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
413202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
413302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
41349ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
413502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
413602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
413702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
4138470aa5bc291ca33d51dda356f38ac2954026da9aAlon Albert        sEventEntitiesProjectionMap.put(Events.DIRTY, Events.DIRTY);
41399ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
4140fa332ecedc0c340109811552407142f6e4f600b2RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
414102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
414202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
414302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
414402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
414502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
414602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
414702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
414802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
414902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
415019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
41519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
41521b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
41539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
41549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
41559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
41569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
41579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
41589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
41599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
41609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
41619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
41639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
41649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
41659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
41669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
41679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
41689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
41699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
417002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
417102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
41729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
41749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
41759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
41769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
41779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
4178361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
4179361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
41809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
41829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
41839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
41849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
41859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
41869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
41879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
41889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4189315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4190315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4191315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4192315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4193315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
41949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
419664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
41979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
419864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * This is called by AccountManager when the set of accounts is updated.
419964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * <p>
420064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * We are overriding this since we need to delete from the
42019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
42027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
42037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
420464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     *
420564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * @param accounts The list of currently active accounts.
42069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4207f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    @Override
42089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
420964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        Thread thread = new AccountsUpdatedThread(accounts);
421064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        thread.start();
421164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
421264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
421364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private class AccountsUpdatedThread extends Thread {
421464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        private Account[] mAccounts;
421564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
421664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountsUpdatedThread(Account[] accounts) {
421764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            mAccounts = accounts;
421864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
421964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
422064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        @Override
422164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        public void run() {
422264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // The process could be killed while the thread runs.  Right now that isn't a problem,
422364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // because we'll just call removeStaleAccounts() again when the provider restarts, but
422464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // if we want to do additional actions we may need to use a service (e.g. start
422564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // EmptyService in onAccountsUpdated() and stop it when we finish here).
422664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
422764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
422864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            removeStaleAccounts(mAccounts);
422964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
423064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
423164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
423264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    /**
423364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * Makes sure there are no entries for accounts that no longer exist.
423464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     */
423564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void removeStaleAccounts(Account[] accounts) {
4236ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4237ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
4238ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
4239ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4240ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4241ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
42429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
424346f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>();
424446f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
42459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
42469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
42479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
42489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
42499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
42509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
42519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
42529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
42539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4254b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            for (String table : new String[]{Tables.CALENDARS}) {
4255ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
42569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
42577cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                Cursor c = mDb.rawQuery("SELECT DISTINCT " +
42582ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_NAME +
42597cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
42602ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_TYPE +
42617cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
42629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
42634cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // ACCOUNT_TYPE_LOCAL is to store calendars not associated
42644cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // with a system account. Typically, a calendar must be
42654cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // associated with an account on the device or it will be
42664cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // deleted.
4267b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    if (c.getString(0) != null
4268b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && c.getString(1) != null
4269b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && !TextUtils.equals(c.getString(1),
4270b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                    CalendarContract.ACCOUNT_TYPE_LOCAL)) {
42719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
42729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
42739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
42749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
42759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
42769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
42779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
42789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
42799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
42809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
4281f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
4282f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
4283f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
42849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
4285b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
42869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
42879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
42889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
42899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
42909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
42919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
42923ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
42933ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
4294dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
42959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
42969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4297636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
4298636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
4299636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
4300636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
4301636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
4302636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
4303636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
4304636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
4305636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
4306636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
4307636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
4308636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
4309636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
4310636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
4311636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
4312636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
4313636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
4314636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
4315636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
43169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
4317