CalendarProvider2.java revision c339afc7df041ebfc5f4587f78cf38562aa23459
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
20d5af586101b6111ca188bb373098309c7c8a4abbAlon Albertimport com.google.common.annotations.VisibleForTesting;
21d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert
22bf61571797b7b6a390d35f16aad7765ea348e5aeAndy McFaddenimport com.android.calendarcommon.DateException;
2393e0bbb921cce7a5cec355521bc570c03c9d6a1cAndy McFaddenimport com.android.calendarcommon.EventRecurrence;
24bf61571797b7b6a390d35f16aad7765ea348e5aeAndy McFaddenimport com.android.calendarcommon.RecurrenceProcessor;
2593e0bbb921cce7a5cec355521bc570c03c9d6a1cAndy McFaddenimport com.android.calendarcommon.RecurrenceSet;
267be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
277be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
28370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
46a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
47a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
50b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
51b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Attendees;
52b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
53b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
542f251c778c06d21ed7693a70f4a1268ff929242eRoboErikimport android.provider.CalendarContract.Colors;
55b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
56b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
57b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
58b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.SyncState;
599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
601edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
61192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
64ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
663b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.io.File;
672ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErikimport java.lang.reflect.Array;
683b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.lang.reflect.Method;
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
70ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
731c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFaddenimport java.util.Iterator;
74dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
75bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFaddenimport java.util.Set;
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
77dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
82b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik * is defined in {@link android.provider.CalendarContract}.
839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
878bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
88d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    static final boolean DEBUG_INSTANCES = false;
899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
907be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
91c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String ACCOUNT_SELECTION_PREFIX = Calendars.ACCOUNT_NAME + "=? AND "
92c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            + Calendars.ACCOUNT_TYPE + "=?";
937be45683e367bd6897daf6444b03be938f8f5eaaErik
94f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final boolean PROFILE = false;
959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
968f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
1019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
1029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
104b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            Events.ORIGINAL_ID,
105c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            Events.ORIGINAL_SYNC_ID,
1069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1079ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
1089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
111b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_ID_INDEX = 3;
112b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_SYNC_ID_INDEX = 4;
1137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] COLORS_PROJECTION = new String[] {
1152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_NAME,
1162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_TYPE,
1172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR_TYPE,
118387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        Colors.COLOR_KEY,
1192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR,
1202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_NAME_INDEX = 0;
1222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_TYPE_INDEX = 1;
1232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_TYPE_INDEX = 2;
1242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX_INDEX = 3;
1252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX = 4;
1262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1274755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private static final String COLOR_FULL_SELECTION = Colors.ACCOUNT_NAME + "=? AND "
1284755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + Colors.ACCOUNT_TYPE + "=? AND " + Colors.COLOR_TYPE + "=? AND " + Colors.COLOR_KEY
1294755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + "=?";
1304755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan
1312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_NAME = Calendars.ACCOUNT_NAME;
1322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_TYPE = Calendars.ACCOUNT_TYPE;
1332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] ACCOUNT_PROJECTION = new String[] {
1342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_NAME,
1352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_TYPE,
1362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_NAME_INDEX = 0;
1382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_TYPE_INDEX = 1;
1392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1401c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    // many tables have _id and event_id; pick a representative version to use as our generic
1411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_ID = Attendees._ID;
1421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_EVENT_ID = Attendees.EVENT_ID;
1431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
1447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_ID,
1461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_EVENT_ID,
1477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
152646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
153646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
154646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
155646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
156646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
157646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
158646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
159646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
160646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
161646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
162646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
163646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
164646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
165646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
166646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
167646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
173ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
176f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    private CalendarInstancesHelper mInstancesHelper;
1779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1788ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
179f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // Due to an issue in Calendar Server restricting the length of the name we
180f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // had to strip it down
1818ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1828ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1838ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1848ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1858ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1863443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
187b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + ", " +
188b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTSTART_2445 + ", " +
189b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTEND_2445 + ", " +
1903443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1913443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
192b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
193b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1943443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
195b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
196b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
197b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_UPDATE_EVENT_SET_DIRTY = "UPDATE " +
198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
199c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            " SET " + Events.DIRTY + "=1" +
200b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
201b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_WHERE_CALENDAR_COLOR = Calendars.ACCOUNT_NAME + "=? AND "
203387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            + Calendars.ACCOUNT_TYPE + "=? AND " + Calendars.CALENDAR_COLOR_KEY + "=?";
2042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
2052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_WHERE_EVENT_COLOR = Events.ACCOUNT_NAME + "=? AND "
206387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            + Events.ACCOUNT_TYPE + "=? AND " + Events.EVENT_COLOR_KEY + "=?";
2072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
20824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    protected static final String SQL_WHERE_ID = GENERIC_ID + "=?";
20924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private static final String SQL_WHERE_EVENT_ID = GENERIC_EVENT_ID + "=?";
2104d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID = Events.ORIGINAL_ID + "=?";
2114d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID = Events.ORIGINAL_ID +
2124d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden            "=? AND " + Events._SYNC_ID + " IS NULL";
213ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
214ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan    private static final String SQL_WHERE_ATTENDEE_BASE =
215ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID
216ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            + " AND " +
217ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
218ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
219b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
220ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " + SQL_WHERE_ATTENDEE_BASE;
221b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
223b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
224ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID +
225ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            " AND " +
226ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
227b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
228b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
2292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
230b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
231b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
232b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
2332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
234b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
235b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
236b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
237b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
238b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
239b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            Tables.EXTENDED_PROPERTIES + "." + CalendarContract.ExtendedProperties._ID + "=?";
240b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
241b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
2422ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                " WHERE " + Calendars.ACCOUNT_NAME + "=? AND " +
2432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    Calendars.ACCOUNT_TYPE + "=?";
244b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_DELETE_FROM_COLORS = "DELETE FROM " + Tables.COLORS + " WHERE "
2462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            + Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?";
2472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
248fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
249fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
250fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
26081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
26181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
26281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
26381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
264b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
26581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
266b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
26781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
26818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
26918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
27018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
27118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
27218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
273b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
27418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
275b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")" + ") LEFT OUTER JOIN " +
27618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
27718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
278b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Attendees.EVENT_ID + "=" +
27918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
280b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
28118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
282b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
283b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.START_DAY + "<=? AND " +
284b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END_DAY + ">=?";
28581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
286b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
287b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.BEGIN + "<=? AND " +
288b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END + ">=?";
2899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
2972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * The sort order is: events with an earlier start time occur first and if
2982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the start times are the same, then events with a later end time occur
2992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * first. The later end time is ordered first so that long-running events in
3002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the calendar views appear first. If the start and end times of two events
3012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * are the same then we sort alphabetically on the title. This isn't
3022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * required for correctness, it just adds a nice touch.
3032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
3042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
3052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
3062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
3072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * A regex for describing how we split search queries into tokens. Keeps
3082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * quoted phrases as one token. "one \"two three\"" ==> ["one" "two three"]
309dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
310dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
311dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
312dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
313dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
314dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
315dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
316dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
317dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
318dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
31981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
320dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
321dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
322dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
323dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
324dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
325dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
326dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
327dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
328dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
32981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
33018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
33118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
33218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
33318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
33418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
335b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_EMAIL + ")";
33618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
33718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
33818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
33918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
34018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
34118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
342b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_NAME + ")";
34318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
34481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
345b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.TITLE,
346b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.DESCRIPTION,
347b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.EVENT_LOCATION,
34818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
34918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
35081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
35181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
352a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
353a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
354a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
355a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
356a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
357a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
358a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
359a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
360a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
361a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
362a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
363a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
364dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
365dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
366dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
367dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
368dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
369bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Set of columns allowed to be altered when creating an exception to a recurring event. */
370bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final HashSet<String> ALLOWED_IN_EXCEPTION = new HashSet<String>();
371bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    static {
372bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // _id, _sync_account, _sync_account_type, dirty, _sync_mark, calendar_id
373bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events._SYNC_ID);
374bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA1);
375bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA7);
37602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA3);
377bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.TITLE);
378bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_LOCATION);
379bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DESCRIPTION);
3802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR);
381387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR_KEY);
382bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.STATUS);
383c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SELF_ATTENDEE_STATUS);
38402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA6);
385bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DTSTART);
386c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // dtend -- set from duration as part of creating the exception
387bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_TIMEZONE);
388bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_END_TIMEZONE);
389bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DURATION);
390bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ALL_DAY);
391bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ACCESS_LEVEL);
392bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.AVAILABILITY);
393bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ALARM);
394bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_EXTENDED_PROPERTIES);
395bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RRULE);
396bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RDATE);
397bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXRULE);
398bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXDATE);
399bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_SYNC_ID);
400bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_INSTANCE_TIME);
401bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // originalAllDay, lastDate
402bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ATTENDEE_DATA);
403bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_MODIFY);
404bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_INVITE_OTHERS);
405bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_SEE_GUESTS);
406bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORGANIZER);
407bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // deleted, original_id, alerts
408bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
409bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
410bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Don't clone these from the base event into the exception event. */
411bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final String[] DONT_CLONE_INTO_EXCEPTION = {
412bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events._SYNC_ID,
413bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA1,
41402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA2,
41502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA3,
41602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA4,
41702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA5,
41802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA6,
419bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA7,
42002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA8,
421c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA9,
422c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA10,
423bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    };
424bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
425bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** set to 'true' to enable debug logging for recurrence exception code */
426bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final boolean DEBUG_EXCEPTION = false;
427bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
428dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
429e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
430e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
4318bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
4328bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
433420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
434420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected CalendarAlarmManager mCalendarAlarm;
435a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
436a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
437a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
438a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
439dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
440a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
441a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
442a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
443dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
444dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
445dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
446dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
447dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
448a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
449a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
450a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
451a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
465420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
469420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
471420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4828bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4838bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4848bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4858bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
486e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
487e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
488e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
489e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
490e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
491e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
492e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
4938bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4948bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
4979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
498ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
499ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
500ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
501f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
502f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
503f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
504ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
505ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
506ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
5079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
508ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
5098bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
5108bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
511dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
512e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
513e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
514ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
515ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
5169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5174caf8d015918f619a67d321a152f150a01022717Andy McFadden        mMetaData = new MetaData(mDbHelper);
5184caf8d015918f619a67d321a152f150a01022717Andy McFadden        mInstancesHelper = new CalendarInstancesHelper(mDbHelper, mMetaData);
5194caf8d015918f619a67d321a152f150a01022717Andy McFadden
5209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
5219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
5229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
5249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
5259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
5269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
5289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
5299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
5309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
531e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
5329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
533ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
534ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
535420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // This is pulled out for testing
536420b7fb569773ae573fbe90c3a9c522d4c368863Erik        initCalendarAlarm();
537e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
538e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
5398bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
5419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
543420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected void initCalendarAlarm() {
544420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm = getOrCreateCalendarAlarmManager();
545420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.getScheduleNextAlarmWakeLock();
546e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
547e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
548e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
549420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mCalendarAlarm == null) {
550420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm = new CalendarAlarmManager(mContext);
551e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
552420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mCalendarAlarm;
553e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
554e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
555ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
556ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
557ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
558ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
559ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
560ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
561ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
562ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
563ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
564ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
565ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
566ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
567ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
568ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
569ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
570ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
57164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void verifyAccounts() {
57264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
57364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        removeStaleAccounts(AccountManager.get(getContext()).getAccounts());
57464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
57564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
57664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
5779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
580315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
5819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
5839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
5859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
5889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
5899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
5909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
591ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
5929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
596315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
597315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
598315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
599315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
600315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
601315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
602315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
603315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
6069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
607315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
608ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
609315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
610315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
611a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
612315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
613315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
614315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
615ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
616315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
617315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
618ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
619315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
620ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
621ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
622ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
623ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
624420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.rescheduleMissedAlarms();
625ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
626ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
627f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
628f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
629f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
630ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
631ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
632ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
633ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
634ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
635f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
636f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
637f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
638ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
6399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
640ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
641ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
642315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
643ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
644ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
6453443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
646ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
647315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
648ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
649ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
650ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
651ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
652ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
653ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
654ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
6553443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
6563443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
657ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
659ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
660ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
661ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6623443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
663f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
664f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
665f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
666f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
667f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
668f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
669f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
670ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6713443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
672ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
673ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
674ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
675ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
676ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
677ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
678ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
679ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
680ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
681ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
682ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
683f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
684f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
685f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
686ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
687ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
688ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
689ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
690ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
691ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
692f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
693f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
694f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
695ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
696ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
697ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
698ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
699ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
700ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
701ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
702ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
703ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
704b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
705b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
706ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
707b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
708dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
709ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
710ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
711ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
712ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
713ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
714ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
715ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
716ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
717ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
718ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
719ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
720f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
721f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
722f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
723ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
724ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
7259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
726ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
727ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
728ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
729ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
730315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
731315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
732ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
733ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
734ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
735ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
736ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
73725e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
738ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
739315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
740315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
741ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
742ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
743f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
744f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
745f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
746ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
747ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
748ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
749315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
750315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
751315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
752315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
753315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
754ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
7569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
7579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
759315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
760315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7661f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7691f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7701f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7711f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7721f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7731f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7741f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
7752ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* selection */, null,
7762ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* sort */,
777d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
778315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
7792ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    instancesTimezone, isHomeTimezone());
7801f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
7811f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
7821f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
7831f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7841f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
7859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
786420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.rescheduleMissedAlarms();
7879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
791b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
794b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        mContentResolver.notifyChange(CalendarContract.CONTENT_URI, null, syncToNetwork);
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
800ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
801ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
809315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
8139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
814fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selection, selectionArgs,
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
816fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden            case SYNCSTATE_ID:
817fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                String selectionWithId = (SyncState._ID + "=?")
818fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                    + (selection == null ? "" : " AND (" + selection + ")");
819fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                // Prepend id to selectionArgs
820fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                selectionArgs = insertSelectionArg(selectionArgs,
821fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        String.valueOf(ContentUris.parseId(uri)));
822fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selectionWithId,
823fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        selectionArgs, sortOrder);
8249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
8261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
8289ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
8299ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
8309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
8321ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
834636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
835b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
83719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
83819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
83919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
84019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
8419ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
8429ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
84319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
84419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
84519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
84619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
847636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
848b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
84919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
85019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
8512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
8522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setTables(Tables.COLORS);
8532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setProjectionMap(sColorsProjectionMap);
8542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                selection = appendAccountFromParameterToSelection(selection, uri);
8552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
8562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
8579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
85843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
859b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
8609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
8619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
86343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
864b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
865636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
866b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
8699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
8709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
884315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
8852ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceQuery(qb, begin, end, projection, selection, selectionArgs,
8862ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        sortOrder, match == INSTANCES_BY_DAY, false /* don't force an expansion */,
887315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
88881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
88981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
89081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
89181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
89281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
89381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
89481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
89581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
89681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
89781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
89881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
89981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
90081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
90181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
902315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
90381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
90481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
9052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceSearchQuery(qb, begin, end, query, projection, selection,
9062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        selectionArgs, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
907315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9086db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
9099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
9109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
9119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
9159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
9169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
9199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
9219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
923315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
924315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
925315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
92702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
929ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan                qb.appendWhere(SQL_WHERE_ATTENDEE_BASE);
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
93202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
934636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
935b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
938b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
94102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
943636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
944b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
947b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
949b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
952b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
954b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
958b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
960636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
961b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
9629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
964b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
967b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
968636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
969b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
9709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
971315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
972b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
973315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
974315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
9849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
9859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
986ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
98739c65e5716e21e863d8de587d139dae85f99422fFred Quintana        if (projection != null && projection.length == 1
98839c65e5716e21e863d8de587d139dae85f99422fFred Quintana                && BaseColumns._COUNT.equals(projection[0])) {
98939c65e5716e21e863d8de587d139dae85f99422fFred Quintana            qb.setProjectionMap(sCountProjectionMap);
99039c65e5716e21e863d8de587d139dae85f99422fFred Quintana        }
99139c65e5716e21e863d8de587d139dae85f99422fFred Quintana
992ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
993ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
994ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
995ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
996ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
997ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
998ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
999ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
10009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
10029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
10039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
1004b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            c.setNotificationUri(mContentResolver, CalendarContract.Events.CONTENT_URI);
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
10079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
10109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
10139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
10149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
10159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
10169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
10179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
10189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
10199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
1020d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1021315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1022315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
10239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
10249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
10262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            long rangeEnd, String[] projection, String selection, String[] selectionArgs,
10272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String sort, boolean searchByDay, boolean forceExpansion,
10282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
10299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
103081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
10319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
10329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
10339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
10349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
1035315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
10369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
10379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
10409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1041315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
1042315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1043b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
10449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
10459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1046315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
1047315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1048b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
10499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10502ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10512ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = new String[] {String.valueOf(rangeEnd),
10528335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
10532ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
10542ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
10552ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
10562ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
10572ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
10582ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
10592ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10608335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
10617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
10629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
106481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
10652ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * Combine a set of arrays in the order they are passed in. All arrays must
10662ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * be of the same type.
10672ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
10682ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static <T> T[] combine(T[]... arrays) {
10692ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (arrays.length == 0) {
10702ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            throw new IllegalArgumentException("Must supply at least 1 array to combine");
10712ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10722ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10732ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int totalSize = 0;
10742ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10752ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            totalSize += array.length;
10762ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10772ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10782ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        T[] finalArray = (T[]) (Array.newInstance(arrays[0].getClass().getComponentType(),
10792ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                totalSize));
10802ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10812ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int currentPos = 0;
10822ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10832ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            int length = array.length;
10842ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            System.arraycopy(array, 0, finalArray, currentPos, length);
10852ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            currentPos += array.length;
10862ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10872ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        return finalArray;
10882ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    }
10892ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10902ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
1091dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1092dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1093dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1094dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1095dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1096dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1097dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1098dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1099dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1100dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1101dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
110281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1103dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1104dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
110581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
110681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
110781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
110881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
110981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
111081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1111dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1112dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1113dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1114dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1115dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1116dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1117dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1118dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1119dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1120dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1121dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1122dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1123dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1124dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
112581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
112681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
112781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
112881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
112981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
113081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
113181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
113281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
113381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
113481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
113581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
113681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
113781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
113881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
113981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
114081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
114181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
114281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
114381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
1144cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * approximation of full-text search across multiple columns.  The set
1145cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * of columns is specified by the SEARCH_COLUMNS constant.
1146cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * <p>
1147cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * Note the "WHERE" token isn't part of the returned string.  The value
1148cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * may be passed into a query as the "HAVING" clause.
114981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
115081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
115181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
115281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
115381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
115481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
115581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
115681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
115781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
115881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
115981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
116081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1161dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1162dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1163dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
116481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
116581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
116681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
116781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
116818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
116918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
117018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
117118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
117281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
117381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
117481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
117581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
117681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
117781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
117818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
117918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
118081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
118118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
118218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
118318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
118481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1185f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1186f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
118718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
118881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
118981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
119081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
119181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
119281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
119381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
119481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
11952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String selection, String[] selectionArgs, String sort, boolean searchByDay,
11962ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
119718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
119881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
119981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1200dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
12012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
12022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
12032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
12042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
12052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
12062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
12072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
12082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
120918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
121018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1211dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1212dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
121381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
121481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
121581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1216315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
121781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
121881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
121981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
122081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
122181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
122218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
122318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
122456292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
122556292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1226315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1227315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1228315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
122956292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1230b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
123181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
123281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
123318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
123418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
123556292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
123656292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1237315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1238315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1239315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
124056292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1241b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
124281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
124381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
124418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
1245c3780839fd044b5d8109860b57a199a2da1d804fMichael Chan                Tables.INSTANCES + "." + Instances._ID /* groupBy */,
1246cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden                searchWhere /* having */, sort);
124781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
124881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
12496db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1250315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1251315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
125281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
12536db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
125443556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
125543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1256315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1257192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
125843556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
125943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1260192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
126143556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1263315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1264b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
12658335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
12668335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
12678335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
12686db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
12699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * table.  Acquires the database lock and calls
12749ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #acquireInstanceRangeLocked(long, long, boolean, boolean, String, boolean)}.
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1279d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1280315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1281315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1283d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1284315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1285315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
12869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
12879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1289315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
12909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
12919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
12929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
12939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1303315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1304315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1305315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1307420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1308315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
13099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
13119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1312d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (DEBUG_INSTANCES) {
1313d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            Log.d(TAG + "-i", "acquireInstanceRange begin=" + begin + " end=" + end +
1314d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    " useMin=" + useMinimumExpansionWindow + " force=" + forceExpansion);
1315d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1316d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1317315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1318315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1319315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1320315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1321315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
13319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1340315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1341315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1342315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1343315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1344315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1345315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1346315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
13477be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
13487be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
13497be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
13507be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1351315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1352315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1353315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1354d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
1355d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1356d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "Wiping instances and expanding from scratch");
1357d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1358d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1360b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1361f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13626db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1365f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1366315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1367315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
13689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1369315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
13707be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
13717be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
13727be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
13737be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
13747be45683e367bd6897daf6444b03be938f8f5eaaErik
13757be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1376315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
13777be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
13787be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
13797be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
13807be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1381315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
13939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
13949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
13959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1397d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1398d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "instances are already expanded");
1399d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1400f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
14019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
14029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
14039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
14089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
14099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1410f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1417f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1422315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
14269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
14279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
14289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
14456db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
144748587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
144848587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1449315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1450315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
14519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1456b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    /**
1457b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     * Determines if the event is recurrent, based on the provided values.
1458b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     */
1459b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalId,
1460b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            String originalSyncId) {
1461b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden        return (!TextUtils.isEmpty(rrule) ||
1462b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(rdate) ||
1463b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalId) ||
1464b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalSyncId));
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1467646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1468646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1469d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
1470646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1471d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * corrects the fields DTSTART, DTEND, and DURATION if necessary.
1472646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1473d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values The values to check and correct
1474d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param modValues Any updates will be stored here.  This may be the same object as
1475d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   <strong>values</strong>.
1476646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1477646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1478d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean fixAllDayTime(ContentValues values, ContentValues modValues) {
1479499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        Integer allDayObj = values.getAsInteger(Events.ALL_DAY);
1480499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        if (allDayObj == null || allDayObj == 0) {
1481d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            return false;
1482d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1483d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1484646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1485646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1486d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtstart = values.getAsLong(Events.DTSTART);
1487d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtend = values.getAsLong(Events.DTEND);
1488d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String duration = values.getAsString(Events.DURATION);
1489d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Time time = new Time();
1490d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String tempValue;
1491d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1492d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // Change dtstart so h,m,s are 0 if necessary.
1493d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.clear(Time.TIMEZONE_UTC);
1494d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.set(dtstart.longValue());
1495d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1496d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.hour = 0;
1497d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.minute = 0;
1498d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.second = 0;
1499d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            modValues.put(Events.DTSTART, time.toMillis(true));
1500d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            neededCorrection = true;
1501d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1502d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1503d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // If dtend exists for this event make sure it's h,m,s are 0.
1504d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (dtend != null) {
1505646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1506d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.set(dtend.longValue());
1507646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1508646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1509646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1510646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1511d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                dtend = time.toMillis(true);
1512d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DTEND, dtend);
1513646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1514646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1515d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1516646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1517d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (duration != null) {
1518d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            int len = duration.length();
1519d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1520d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             * in the seconds format, and if so converts it to days.
1521d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             */
1522d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (len == 0) {
1523d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = null;
1524d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else if (duration.charAt(0) == 'P' &&
1525d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    duration.charAt(len - 1) == 'S') {
1526d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int seconds = Integer.parseInt(duration.substring(1, len - 1));
1527d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1528d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = "P" + days + "D";
1529d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DURATION, duration);
1530d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                neededCorrection = true;
1531646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1532646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1533d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1534646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1535646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1536646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1537bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1538bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1539bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Determines whether the strings in the set name columns that may be overridden
1540bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * when creating a recurring event exception.
1541bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1542bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This uses a white list because it screens out unknown columns and is a bit safer to
1543bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * maintain than a black list.
1544bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1545bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private void checkAllowedInException(Set<String> keys) {
1546bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        for (String str : keys) {
1547bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!ALLOWED_IN_EXCEPTION.contains(str.intern())) {
1548bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                throw new IllegalArgumentException("Exceptions can't overwrite " + str);
1549bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1550bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1551bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1552bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1553bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
155432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * Splits a recurrent event at a specified instance.  This is useful when modifying "this
155532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * and all future events".
155632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
155732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence rule has a COUNT specified, we need to split that at the point of the
155832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * exception.  If the exception is instance N (0-based), the original COUNT is reduced
155932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * to N, and the exception's COUNT is set to (COUNT - N).
156032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
156132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence doesn't have a COUNT, we need to update or introduce an UNTIL value,
156232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * so that the original recurrence will end just before the exception instance.  (Note
156332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * that UNTIL dates are inclusive.)
156432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
156532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * This should not be used to update the first instance ("update all events" action).
1566bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
156732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @param values The original event values; must include EVENT_TIMEZONE and DTSTART.
156832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        The RRULE value may be modified (with the expectation that this will propagate
156932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        into the exception event).
1570bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param endTimeMillis The time before which the event must end (i.e. the start time of the
1571bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *        exception event instance).
157232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @return Values to apply to the original event.
1573bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1574bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static ContentValues setRecurrenceEnd(ContentValues values, long endTimeMillis) {
157532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        boolean origAllDay = values.getAsBoolean(Events.ALL_DAY);
157632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        String origRrule = values.getAsString(Events.RRULE);
1577bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
157832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        EventRecurrence origRecurrence = new EventRecurrence();
157932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        origRecurrence.parse(origRrule);
1580bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
158132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        // Get the start time of the first instance in the original recurrence.
158232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        long startTimeMillis = values.getAsLong(Events.DTSTART);
1583bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time dtstart = new Time();
1584bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.timezone = values.getAsString(Events.EVENT_TIMEZONE);
158532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        dtstart.set(startTimeMillis);
1586bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1587bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ContentValues updateValues = new ContentValues();
158832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
158932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        if (origRecurrence.count > 0) {
159032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            /*
159132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * Generate the full set of instances for this recurrence, from the first to the
159232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * one just before endTimeMillis.  The list should never be empty, because this method
159332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * should not be called for the first instance.  All we're really interested in is
159432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * the *number* of instances found.
159532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             */
159632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceSet recurSet = new RecurrenceSet(values);
159732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceProcessor recurProc = new RecurrenceProcessor();
159832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            long[] recurrences;
159932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            try {
160032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis);
160132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            } catch (DateException de) {
160232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException(de);
160332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
160432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
160532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (recurrences.length == 0) {
160632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException("can't use this method on first instance");
160732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
160832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
160932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            EventRecurrence excepRecurrence = new EventRecurrence();
16101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            excepRecurrence.parse(origRrule); // TODO: add/use a copy constructor to EventRecurrence
161132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            excepRecurrence.count -= recurrences.length;
161232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            values.put(Events.RRULE, excepRecurrence.toString());
161332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
161432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.count = recurrences.length;
161532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
161632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        } else {
161732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            Time untilTime = new Time();
161832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
161932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // The "until" time must be in UTC time in order for Google calendar
162032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // to display it properly. For all-day events, the "until" time string
162132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // must include just the date field, and not the time field. The
162232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // repeating events repeat up to and including the "until" time.
162332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.timezone = Time.TIMEZONE_UTC;
162432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
162532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // Subtract one second from the exception begin time to get the "until" time.
162632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis)
162732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (origAllDay) {
162832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.hour = untilTime.minute = untilTime.second = 0;
162932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.allDay = true;
163032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.normalize(false);
163132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
163232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // This should no longer be necessary -- DTSTART should already be in the correct
163332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // format for an all-day event.
163432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.hour = dtstart.minute = dtstart.second = 0;
163532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.allDay = true;
163632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.timezone = Time.TIMEZONE_UTC;
163732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
163832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.until = untilTime.format2445();
163932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        }
164032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
164132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        updateValues.put(Events.RRULE, origRecurrence.toString());
1642bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.DTSTART, dtstart.normalize(true));
1643bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        return updateValues;
1644bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1645bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1646bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1647bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Handles insertion of an exception to a recurring event.
1648bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1649bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * There are two modes, selected based on the presence of "rrule" in modValues:
1650bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <ol>
1651bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Create a single instance exception ("modify current event only").
1652bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Cap the original event, and create a new recurring event ("modify this and all
1653bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * future events").
1654bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * </ol>
1655bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This may be used for "modify all instances of the event" by simply selecting the
1656bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * very first instance as the exception target.  In that case, the ID of the "new"
1657bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * exception event will be the same as the originalEventId.
1658bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1659bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param originalEventId The _id of the event to be modified
1660bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param modValues Event columns to update
1661c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden     * @param callerIsSyncAdapter Set if the content provider client is the sync adapter
1662bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return the ID of the new "exception" event, or -1 on failure
1663bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1664c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden    private long handleInsertException(long originalEventId, ContentValues modValues,
1665c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            boolean callerIsSyncAdapter) {
1666bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (DEBUG_EXCEPTION) {
1667bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            Log.i(TAG, "RE: values: " + modValues.toString());
1668bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1669bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1670bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Make sure they have specified an instance via originalInstanceTime.
1671bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Long originalInstanceTime = modValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1672bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (originalInstanceTime == null) {
1673bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            throw new IllegalArgumentException("Exceptions must specify " +
1674bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Events.ORIGINAL_INSTANCE_TIME);
1675bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1676bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1677bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Check for attempts to override values that shouldn't be touched.
1678bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        checkAllowedInException(modValues.keySet());
1679bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1680c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // If this isn't the sync adapter, set the "dirty" flag in any Event we modify.
1681c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        if (!callerIsSyncAdapter) {
1682c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            modValues.put(Events.DIRTY, true);
1683c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        }
1684c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1685bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Wrap all database accesses in a transaction.
1686bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        mDb.beginTransaction();
1687bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Cursor cursor = null;
1688bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        try {
1689bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: verify that there's an instance corresponding to the specified time
1690bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (does this matter? it's weird, but not fatal?)
1691bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1692bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Grab the full set of columns for this event.
1693bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor = mDb.query(Tables.EVENTS, null /* columns */,
1694bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    SQL_WHERE_ID, new String[] { String.valueOf(originalEventId) },
1695bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    null /* groupBy */, null /* having */, null /* sortOrder */);
1696bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor.getCount() != 1) {
1697bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event ID " + originalEventId + " lookup failed (count is " +
1698bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        cursor.getCount() + ")");
1699bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1700bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1701bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //DatabaseUtils.dumpCursor(cursor);
1702bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
17032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If there's a color index check that it's valid
1704387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_index = modValues.getAsString(Events.EVENT_COLOR_KEY);
17052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_index)) {
17062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                int calIdCol = cursor.getColumnIndex(Events.CALENDAR_ID);
17072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calId = cursor.getLong(calIdCol);
17082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
17092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
17102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calId != null) {
17112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calId);
17122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
17132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
17142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
17152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
17162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
17172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_index, Colors.TYPE_EVENT);
17182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
17192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1720bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1721bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * Verify that the original event is in fact a recurring event by checking for the
1722bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * presence of an RRULE.  If it's there, we assume that the event is otherwise
1723bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * properly constructed (e.g. no DTEND).
1724bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1725bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor.moveToFirst();
1726bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int rruleCol = cursor.getColumnIndex(Events.RRULE);
1727bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (TextUtils.isEmpty(cursor.getString(rruleCol))) {
1728bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event has no rrule");
1729bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1730bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1731bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (DEBUG_EXCEPTION) {
1732bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.d(TAG, "RE: old RRULE is " + cursor.getString(rruleCol));
1733bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1734bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1735bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Verify that the original event is not itself a (single-instance) exception.
1736bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int originalIdCol = cursor.getColumnIndex(Events.ORIGINAL_ID);
1737bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!TextUtils.isEmpty(cursor.getString(originalIdCol))) {
1738bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event is an exception");
1739bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1740bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1741bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1742bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createSingleException = TextUtils.isEmpty(modValues.getAsString(Events.RRULE));
1743bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1744bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: check for the presence of an existing exception on this event+instance?
1745bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       The caller should be modifying that, not creating another exception.
1746bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (Alternatively, we could do that for them.)
1747bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1748bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Create a new ContentValues for the new event.  Start with the original event,
1749bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // and drop in the new caller-supplied values.  This will set originalInstanceTime.
1750bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            ContentValues values = new ContentValues();
1751bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
1752f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor.close();
1753f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor = null;
1754bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1755b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            // TODO: if we're changing this to an all-day event, we should ensure that
1756b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       hours/mins/secs on DTSTART are zeroed out (before computing DTEND).
1757b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       See fixAllDayTime().
1758b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1759bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createNewEvent = true;
1760bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createSingleException) {
1761bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1762bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Save a copy of a few fields that will migrate to new places.
1763bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1764bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _id = values.getAsString(Events._ID);
1765bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _sync_id = values.getAsString(Events._SYNC_ID);
1766bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1767bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1768bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1769bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Wipe out some fields that we don't want to clone into the exception event.
1770bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1771bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                for (String str : DONT_CLONE_INTO_EXCEPTION) {
1772bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(str);
1773bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1774bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1775bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1776bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Merge the new values on top of the existing values.  Note this sets
1777bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * originalInstanceTime.
1778bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1779bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.putAll(modValues);
1780bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1781bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1782bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Copy some fields to their "original" counterparts:
1783bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _id --> original_id
1784bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _sync_id --> original_sync_id
1785bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   allDay --> originalAllDay
1786bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1787bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this event hasn't been sync'ed with the server yet, the _sync_id field will
1788bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * be null.  We will need to fill original_sync_id in later.  (May not be able to
1789bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * do it right when our own _sync_id field gets populated, because the order of
1790bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * events from the server may not be what we want -- could update the exception
1791bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * before updating the original event.)
1792bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1793bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * _id is removed later (right before we write the event).
1794bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1795bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ID, _id);
1796bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_SYNC_ID, _sync_id);
1797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ALL_DAY, allDay);
1798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // Mark the exception event status as "tentative", unless the caller has some
1800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // other value in mind (like STATUS_CANCELED).
1801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (!values.containsKey(Events.STATUS)) {
1802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
1803bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1804bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1805bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // We're converting from recurring to non-recurring.  Clear out RRULE and replace
1806bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // DURATION with DTEND.
1807c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                values.remove(Events.RRULE);
1808bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1809bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Duration duration = new Duration();
1810bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String durationStr = values.getAsString(Events.DURATION);
1811bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                try {
1812bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    duration.parse(durationStr);
1813bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } catch (Exception ex) {
1814bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // NullPointerException if the original event had no duration.
1815bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // DateException if the duration was malformed.
1816bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Bad duration in recurring event: " + durationStr, ex);
1817bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1818bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1819bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1820c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1821c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * We want to compute DTEND as an offset from the start time of the instance.
1822c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If the caller specified a new value for DTSTART, we want to use that; if not,
1823c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the DTSTART in "values" will be the start time of the first instance in the
1824c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * recurrence, so we want to replace it with ORIGINAL_INSTANCE_TIME.
1825c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1826c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                long start;
1827c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.DTSTART)) {
1828c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.DTSTART);
1829c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                } else {
1830c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1831c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    values.put(Events.DTSTART, start);
1832c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1833bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.DTEND, start + duration.getMillis());
1834bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1835c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    Log.d(TAG, "RE: ORIG_INST_TIME=" + start +
1836c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            ", duration=" + duration.getMillis() +
1837bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            ", generated DTEND=" + values.getAsLong(Events.DTEND));
1838bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
183985c09a31bcc3a18e173428bf7b628cec2834bebcAndy McFadden                values.remove(Events.DURATION);
1840bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1841bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1842bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * We're going to "split" the recurring event, making the old one stop before
1843bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * this instance, and creating a new recurring event that starts here.
1844bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1845bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * No need to fill out the "original" fields -- the new event is not tied to
1846bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * the previous event in any way.
1847bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1848bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this is the first event in the series, we can just update the existing
1849bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * event with the values.
1850bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1851bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean canceling = (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
1852bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1853bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (originalInstanceTime.equals(values.getAsLong(Events.DTSTART))) {
1854bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1855bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Update fields in the existing event.  Rather than use the merged data
1856bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * from the cursor, we just do the update with the new value set after
1857bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * removing the ORIGINAL_INSTANCE_TIME entry.
1858bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1859bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (canceling) {
1860bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        // TODO: should we just call deleteEventInternal?
1861bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "Note: canceling entire event via exception call");
1862bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1863bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1864bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: updating full event");
1865bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1866ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    if (!validateRecurrenceRule(modValues)) {
1867ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        throw new IllegalArgumentException("Invalid recurrence rule: " +
1868ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                                values.getAsString(Events.RRULE));
1869ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    }
1870bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    modValues.remove(Events.ORIGINAL_INSTANCE_TIME);
1871bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
1872bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1873bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    createNewEvent = false; // skip event creation and related-table cloning
1874bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } else {
1875bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1876bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: splitting event");
1877bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1878bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1879bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
188032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Cap the original event so it ends just before the target instance.  In
188132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * some cases (nonzero COUNT) this will also update the RRULE in "values",
188232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * so that the exception we're creating terminates appropriately.  If a
188332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * new RRULE was specified by the caller, the new rule will overwrite our
188432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * changes when we merge the new values in below (which is the desired
188532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * behavior).
1886bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1887bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    ContentValues splitValues = setRecurrenceEnd(values, originalInstanceTime);
1888bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, splitValues, SQL_WHERE_ID,
1889bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1890bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1891bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
189232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Prepare the new event.  We remove originalInstanceTime, because we're now
1893bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * creating a new event rather than an exception.
1894bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     *
1895bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * We're always cloning a non-exception event (we tested to make sure the
1896bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * event doesn't specify original_id, and we don't allow original_id in the
1897bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * modValues), so we shouldn't end up creating a new event that looks like
1898bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * an exception.
1899bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1900bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.putAll(modValues);
1901bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(Events.ORIGINAL_INSTANCE_TIME);
1902bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1903c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            }
1904bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1905bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            long newEventId;
1906bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createNewEvent) {
1907bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.remove(Events._ID);      // don't try to set this explicitly
1908be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
1909be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(values, null);
1910be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
1911be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(values);
1912be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
1913bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1914bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = mDb.insert(Tables.EVENTS, null, values);
1915bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (newEventId < 0) {
1916bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Unable to add exception to recurring event");
1917bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Values: " + values);
1918bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1919bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1920bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1921bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.d(TAG, "RE: new ID is " + newEventId);
1922bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1923bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1924b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                // TODO: do we need to do something like this?
1925b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                //updateEventRawTimesLocked(id, updatedValues);
1926b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1927b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1928b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Force re-computation of the Instances associated with the recurrence event.
1929b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1930b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, newEventId, true, mDb);
1931b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1932bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1933bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Some of the other tables (Attendees, Reminders, ExtendedProperties) reference
1934c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the Event ID.  We need to copy the entries from the old event, filling in the
1935c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * new event ID, so that somebody doing a SELECT on those tables will find
1936c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * matching entries.
1937bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1938bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                CalendarDatabaseHelper.copyEventRelatedTables(mDb, newEventId, originalEventId);
1939c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1940c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1941c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If we modified Event.selfAttendeeStatus, we need to keep the corresponding
1942c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * entry in the Attendees table in sync.
1943c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1944c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
1945c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    /*
1946c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * Each Attendee is identified by email address.  To find the entry that
1947c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * corresponds to "self", we want to compare that address to the owner of
1948c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * the Calendar.  We're expecting to find one matching entry in Attendees.
1949c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     */
1950c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    long calendarId = values.getAsLong(Events.CALENDAR_ID);
1951f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    String accountName = getOwner(calendarId);
1952f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden
1953f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    if (accountName != null) {
1954c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        ContentValues attValues = new ContentValues();
1955c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        attValues.put(Attendees.ATTENDEE_STATUS,
1956c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                modValues.getAsString(Events.SELF_ATTENDEE_STATUS));
1957c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1958c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (DEBUG_EXCEPTION) {
1959c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.d(TAG, "Updating attendee status for event=" + newEventId +
1960c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    " name=" + accountName + " to " +
1961c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    attValues.getAsString(Attendees.ATTENDEE_STATUS));
1962c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1963c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        int count = mDb.update(Tables.ATTENDEES, attValues,
1964c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
1965c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                new String[] { String.valueOf(newEventId), accountName });
1966b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                        if (count != 1 && count != 2) {
1967b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // We're only expecting one matching entry.  We might briefly see
1968b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // two during a server sync.
19697148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                            Log.e(TAG, "Attendee status update on event=" + newEventId
19707148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                    + " touched " + count + " rows. Expected one or two rows.");
1971b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            if (false) {
1972b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                // This dumps PII in the log, don't ship with it enabled.
1973b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                Cursor debugCursor = mDb.query(Tables.ATTENDEES, null,
1974b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        Attendees.EVENT_ID + "=? AND " +
1975b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                            Attendees.ATTENDEE_EMAIL + "=?",
1976b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        new String[] { String.valueOf(newEventId), accountName },
1977b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        null, null, null);
1978b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                DatabaseUtils.dumpCursor(debugCursor);
1979b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            }
1980b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            throw new RuntimeException("Status update WTF");
1981c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1982c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    }
1983c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1984bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1985b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1986b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Update any Instances changed by the update to this Event.
1987b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1988b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, originalEventId, false, mDb);
1989bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = originalEventId;
1990bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1991bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1992bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.setTransactionSuccessful();
1993bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            return newEventId;
1994bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        } finally {
1995bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor != null) {
1996bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                cursor.close();
1997bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1998bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.endTransaction();
1999bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
2000bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
2001bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
2002222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    /**
2003222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Fills in the originalId column for previously-created exceptions to this event.  If
2004222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this event is not recurring or does not have a _sync_id, this does nothing.
2005222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2006222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * The server might send exceptions before the event they refer to.  When
2007222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this happens, the originalId field will not have been set in the
2008222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * exception events (it's the recurrence events' _id field, so it can't be
2009222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * known until the recurrence event is created).  When we add a recurrence
2010222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * event with a non-empty _sync_id field, we write that event's _id to the
2011222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * originalId field of any events whose originalSyncId matches _sync_id.
2012222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2013222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Note _sync_id is only expected to be unique within a particular calendar.
2014222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     *
2015222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param id The ID of the Event
2016222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param values Values for the Event being inserted
2017222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     */
2018222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    private void backfillExceptionOriginalIds(long id, ContentValues values) {
2019222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String syncId = values.getAsString(Events._SYNC_ID);
2020222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2021222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rdate = values.getAsString(Events.RDATE);
2022222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String calendarId = values.getAsString(Events.CALENDAR_ID);
2023222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2024222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        if (TextUtils.isEmpty(syncId) || TextUtils.isEmpty(calendarId) ||
2025222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                (TextUtils.isEmpty(rrule) && TextUtils.isEmpty(rdate))) {
2026222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            // Not a recurring event, or doesn't have a server-provided sync ID.
2027222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            return;
2028222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        }
2029222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2030222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        ContentValues originalValues = new ContentValues();
2031222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        originalValues.put(Events.ORIGINAL_ID, id);
2032222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        mDb.update(Tables.EVENTS, originalValues,
2033222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                Events.ORIGINAL_SYNC_ID + "=? AND " + Events.CALENDAR_ID + "=?",
2034222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                new String[] { syncId, calendarId });
2035222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    }
2036222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
20379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2038b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
2039ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
20409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
20419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
20430739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
20440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                null /* selection */, null /* selection args */);
20459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
2049bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case SYNCSTATE:
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
20519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
20537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2054c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
20557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
2060e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
2061be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
2062be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(updatedValues, null);
2063be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
2064be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(updatedValues);
2065be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
2066e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
2067e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
20699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
20709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calendar_id = updatedValues.getAsLong(Events.CALENDAR_ID);
20732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calendar_id == null) {
20742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // validateEventData checks this for non-sync adapter
20752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // inserts
20762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("New events must specify a calendar id");
20772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
20782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color is valid if it is being set
2079387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = updatedValues.getAsString(Events.EVENT_COLOR_KEY);
20802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
20812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calendar_id);
20822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = null;
20832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = null;
20842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
20852f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
20862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
20872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
20882f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, color_id,
20892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_EVENT);
20902f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    updatedValues.put(Events.EVENT_COLOR, color);
20912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
20932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!updatedValues.containsKey(Events.ORGANIZER)) {
20942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    owner = getOwner(calendar_id);
20959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
20979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
20989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
20999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
21009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
210334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
210434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
210534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    long originalId = getOriginalId(updatedValues
210634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsString(Events.ORIGINAL_SYNC_ID));
210734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (originalId != -1) {
210834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_ID, originalId);
210934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
211034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
211134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && updatedValues.containsKey(Events.ORIGINAL_ID)) {
211234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    String originalSyncId = getOriginalSyncId(updatedValues
211334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsLong(Events.ORIGINAL_ID));
211434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (!TextUtils.isEmpty(originalSyncId)) {
211534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
211634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
211734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                }
2118d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (fixAllDayTime(updatedValues, updatedValues)) {
2119f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2120f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
2121f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
2122f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
2123646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
21241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                updatedValues.remove(Events.HAS_ALARM);     // should not be set by caller
2125c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
21269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
21279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
21289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
2129f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                    mInstancesHelper.updateInstancesLocked(updatedValues, id,
2130f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                            true /* new event */, mDb);
21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
21339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
21349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
21369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
21372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            owner = getOwner(calendar_id);
21389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2141b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
2142222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                    backfillExceptionOriginalIds(id, values);
2143222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2144dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
21459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2147bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID:
2148bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long originalEventId = ContentUris.parseId(uri);
2149c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                id = handleInsertException(originalEventId, values, callerIsSyncAdapter);
2150bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                break;
21519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
215282b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden                // TODO: verify that all required fields are present
21539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
21549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
2155c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
21569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
2157c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                            Calendars.ACCOUNT_TYPE);
21589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
2159fa332ecedc0c340109811552407142f6e4f600b2RoboErik                    String eventsUrl = values.getAsString(Calendars.CAL_SYNC1);
21601b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
21619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2162387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String cal_color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
21632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(cal_color_id)) {
21642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
21652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
21662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, cal_color_id,
21672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_CALENDAR);
21682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    values.put(Calendars.CALENDAR_COLOR, color);
21692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2171dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
21742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // verifyTransactionAllowed requires this be from a sync
21752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter, all of the required fields are marked NOT NULL in
21762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // the db. TODO Do we need explicit checks here or should we
21772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // just let sqlite throw if something isn't specified?
21782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = uri.getQueryParameter(Colors.ACCOUNT_NAME);
21792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = uri.getQueryParameter(Colors.ACCOUNT_TYPE);
2180387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String colorIndex = values.getAsString(Colors.COLOR_KEY);
21812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
21822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Account name and type must be non"
21832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            + " empty parameters for " + uri);
21842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21852f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(colorIndex)) {
21862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("COLOR_INDEX must be non empty for " + uri);
21872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21882f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!values.containsKey(Colors.COLOR_TYPE) || !values.containsKey(Colors.COLOR)) {
21892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException(
21902f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            "New colors must contain COLOR_TYPE and COLOR");
21912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Make sure the account we're inserting for is the same one the
21932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter is claiming to be. TODO should we throw if they
21942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // aren't the same?
21952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_NAME, accountName);
21962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_TYPE, accountType);
21972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
21982f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color doesn't already exist
21992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = null;
22002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
22014755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    final long colorType = values.getAsLong(Colors.COLOR_TYPE);
22024755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
22032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.getCount() != 0) {
22044755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        throw new IllegalArgumentException("color type " + colorType
22054755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                                + " and index " + colorIndex
22067148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                + " already exists for account and type provided");
22072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
22082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
22092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null)
22102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
22112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                id = mDbHelper.colorsInsert(values);
22132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
22149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
22159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
22169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
22179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
22189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
22209ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Attendees.EVENT_ID);
22219ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
22229ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
22237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22249ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.attendeesInsert(values);
22259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
22279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
22301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            {
22311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
22321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventIdObj == null) {
22339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
22341c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            + "contain a numeric event_id");
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
22371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventIdObj);
22381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    setEventDirty(eventIdObj);
22397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22409ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.remindersInsert(values);
22419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // We know this event has at least one reminder, so make sure "hasAlarm" is 1.
22431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                setHasAlarm(eventIdObj, 1);
22441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
22459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
22479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
22489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2249420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
22509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
22582fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
22592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
2262b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                if (!values.containsKey(CalendarContract.ExtendedProperties.EVENT_ID)) {
22639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
22649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
22659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2267b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    final Long eventId = values
2268b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            .getAsLong(CalendarContract.ExtendedProperties.EVENT_ID);
22699ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
22709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
22717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22729ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.extendedPropertiesInsert(values);
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22743b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            case EMMA:
22753b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                // Special target used during code-coverage evaluation.
22763b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                handleEmmaRequest(values);
22773b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                break;
22789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
22846db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2285315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
22867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
22939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2298e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
22993b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * Handles special commands related to EMMA code-coverage testing.
23003b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     *
23013b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * @param values Parameters from the caller.
23023b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     */
23033b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static void handleEmmaRequest(ContentValues values) {
23043b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        /*
23053b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * This is not part of the public API, so we can't share constants with the CTS
23063b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * test code.
23073b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         *
23083b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * Bad requests, or attempting to request EMMA coverage data when the coverage libs
23093b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * aren't linked in, will cause an exception.
23103b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         */
23113b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        String cmd = values.getAsString("cmd");
23123b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        if (cmd.equals("start")) {
23133b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // We'd like to reset the coverage data, but according to FAQ item 3.14 at
23143b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // http://emma.sourceforge.net/faq.html, this isn't possible in 2.0.
23153b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            Log.d(TAG, "Emma coverage testing started");
23163b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        } else if (cmd.equals("stop")) {
23173b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // Call com.vladium.emma.rt.RT.dumpCoverageData() to cause a data dump.  We
23183b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // may not have been built with EMMA, so we need to do this through reflection.
23193b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            String filename = values.getAsString("outputFileName");
23203b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23213b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            File coverageFile = new File(filename);
23223b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            try {
23233b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
23243b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
23253b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        coverageFile.getClass(), boolean.class, boolean.class);
23263b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23273b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                dumpCoverageMethod.invoke(null, coverageFile, false /*merge*/,
23283b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        false /*stopDataCollection*/);
23293b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Log.d(TAG, "Emma coverage data written to " + filename);
23303b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            } catch (Exception e) {
23313b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                throw new RuntimeException("Emma coverage dump failed", e);
23323b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            }
23333b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        }
23343b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    }
23353b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23363b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    /**
23375ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * Validates the recurrence rule, if any.  We allow single- and multi-rule RRULEs.
2338ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * <p>
23395ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * TODO: Validate RDATE, EXRULE, EXDATE (possibly passing in an indication of whether we
23405ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * believe we have the full set, so we can reject EXRULE when not accompanied by RRULE).
2341ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     *
2342ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * @return A boolean indicating successful validation.
2343ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     */
2344ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    private boolean validateRecurrenceRule(ContentValues values) {
2345ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2346ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2347ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        if (!TextUtils.isEmpty(rrule)) {
23485ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            String[] ruleList = rrule.split("\n");
23495ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            for (String recur : ruleList) {
23505ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                EventRecurrence er = new EventRecurrence();
23515ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                try {
23525ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    er.parse(recur);
23535ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                } catch (EventRecurrence.InvalidFormatException ife) {
23545ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    Log.w(TAG, "Invalid recurrence rule: " + recur);
2355bfea6da707f8d352432096371e7da76c230d9059Michael Chan                    dumpEventNoPII(values);
23565ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    return false;
23575ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                }
2358ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2359ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        }
2360ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2361ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        return true;
2362ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    }
2363ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2364bfea6da707f8d352432096371e7da76c230d9059Michael Chan    private void dumpEventNoPII(ContentValues values) {
2365bfea6da707f8d352432096371e7da76c230d9059Michael Chan        if (values == null) {
2366bfea6da707f8d352432096371e7da76c230d9059Michael Chan            return;
2367bfea6da707f8d352432096371e7da76c230d9059Michael Chan        }
2368bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2369bfea6da707f8d352432096371e7da76c230d9059Michael Chan        StringBuilder bob = new StringBuilder();
2370bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("dtStart:       ").append(values.getAsLong(Events.DTSTART));
2371bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndtEnd:         ").append(values.getAsLong(Events.DTEND));
2372bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nall_day:       ").append(values.getAsInteger(Events.ALL_DAY));
2373bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ntz:            ").append(values.getAsString(Events.EVENT_TIMEZONE));
2374bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndur:           ").append(values.getAsString(Events.DURATION));
2375bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrrule:         ").append(values.getAsString(Events.RRULE));
2376bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrdate:         ").append(values.getAsString(Events.RDATE));
2377bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nlast_date:     ").append(values.getAsLong(Events.LAST_DATE));
2378bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2379bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nid:            ").append(values.getAsLong(Events._ID));
2380bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nsync_id:       ").append(values.getAsString(Events._SYNC_ID));
2381bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_id:        ").append(values.getAsLong(Events.ORIGINAL_ID));
2382bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_sync_id:   ").append(values.getAsString(Events.ORIGINAL_SYNC_ID));
2383bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_inst_time: ").append(values.getAsLong(Events.ORIGINAL_INSTANCE_TIME));
2384bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_all_day:   ").append(values.getAsInteger(Events.ORIGINAL_ALL_DAY));
2385bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2386bfea6da707f8d352432096371e7da76c230d9059Michael Chan        Log.i(TAG, bob.toString());
2387bfea6da707f8d352432096371e7da76c230d9059Michael Chan    }
2388bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2389ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    /**
239062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Do some scrubbing on event data before inserting or updating. In particular make
239162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * dtend, duration, etc make sense for the type of event (regular, recurrence, exception).
239262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Remove any unexpected fields.
2393e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
239462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param values the ContentValues to insert.
239562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param modValues if non-null, explicit null entries will be added here whenever something
239662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     *   is removed from <strong>values</strong>.
2397e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
239862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden    private void scrubEventData(ContentValues values, ContentValues modValues) {
2399e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2400e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2401e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2402e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2403c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_SYNC_ID));
2404e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2405e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2406e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2407e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2408e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2409e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2410ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            // rrule is a valid recurrence rule
2411e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2412e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2413e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2414ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            if (!validateRecurrenceRule(values)) {
2415ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2416ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        values.getAsString(Events.RRULE));
2417ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2418e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
241962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DTEND, ORIGINAL_SYNC_ID, ORIGINAL_INSTANCE_TIME");
2420e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
242162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence: " + values);
2422e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2423e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2424c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                values.remove(Events.ORIGINAL_SYNC_ID);
2425e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
242662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
242762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DTEND);
242862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_SYNC_ID);
242962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_INSTANCE_TIME);
243062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2431e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2432e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2433e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2434e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2435e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2436e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2437e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2438e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2439e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2440e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2441e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
244262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2443e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
244462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence exception: " + values);
2445e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2446e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
244762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
244862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
244962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2450e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2451e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2452e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2453e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2454e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2455e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2456e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2457e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2458e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2459e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2460e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
246162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2462e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
246362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for event: " + values);
2464e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2465e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
246662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
246762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
246862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2469e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2470e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2471e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2472e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
2473d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2474d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Validates event data.  Pass in the full set of values for the event (i.e. not just
2475d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * a part that's being updated).
2476d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2477d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values Event data.
2478d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws IllegalArgumentException if bad data is found.
2479d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
2480d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private void validateEventData(ContentValues values) {
248182b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.CALENDAR_ID))) {
248282b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include a calendar_id");
248382b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
248482b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.EVENT_TIMEZONE))) {
248582b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include an eventTimezone");
248682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
248782b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden
2488d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtstart = values.getAsLong(Events.DTSTART) != null;
2489d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2490d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2491d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2492d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2493d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasRrule || hasRdate) {
2494d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!validateRecurrenceRule(values)) {
2495d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2496d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        values.getAsString(Events.RRULE));
2497d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
2498d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2499d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
2500d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDtstart) {
2501bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2502d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTSTART cannot be empty.");
2503d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2504d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDuration && !hasDtend) {
2505bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2506d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
2507d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    "an event.");
2508d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2509d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasDuration && hasDtend) {
2510bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2511d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("Cannot have both DTEND and DURATION in an event");
2512d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2513d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
2514d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
25159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private void setEventDirty(long eventId) {
25169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Object[] {eventId});
25177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
25187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
251934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private long getOriginalId(String originalSyncId) {
252034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (TextUtils.isEmpty(originalSyncId)) {
252134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return -1;
252234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
252334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
252434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        long originalId = -1;
252534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
252634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
252734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
252834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._SYNC_ID + "=?", new String[] {originalSyncId}, null);
252934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
253034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalId = c.getLong(0);
253134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
253234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
253334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
253434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
253534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
253634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
253734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalId;
253834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
253934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
254034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private String getOriginalSyncId(long originalId) {
254134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (originalId == -1) {
254234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return null;
254334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
254434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
254534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        String originalSyncId = null;
254634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
254734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
254834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
254934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
255034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
255134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalSyncId = c.getString(0);
255234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
255334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
255434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
255534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
255634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
255734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
255834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalSyncId;
255934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
256034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
25614755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private Cursor getColorByTypeIndex(String accountName, String accountType, long colorType,
25624755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            String colorIndex) {
25634755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        return mDb.query(Tables.COLORS, COLORS_PROJECTION, COLOR_FULL_SELECTION, new String[] {
25644755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                accountName, accountType, Long.toString(colorType), colorIndex
25654755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        }, null, null, null);
25662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
25672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
25689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2569f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * Gets a calendar's "owner account", i.e. the e-mail address of the owner of the calendar.
2570f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     *
2571f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * @param calId The calendar ID.
25729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
25739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
25749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2575f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2576f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2577f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2578f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2579f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2580f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
25839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
25849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
25859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
25869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
25879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
25899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
25909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2591f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2592f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2593f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
25949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
25959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
25979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
25989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
25999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
26009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private Account getAccount(long calId) {
26062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Account account = null;
26072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor cursor = null;
26082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
26092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
26102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ACCOUNT_PROJECTION, null /* selection */, null /* selectionArgs */,
26112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    null /* sort */);
26122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor == null || !cursor.moveToFirst()) {
26132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (Log.isLoggable(TAG, Log.DEBUG)) {
26142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
26152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
26162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                return null;
26172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
26182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            account = new Account(cursor.getString(ACCOUNT_NAME_INDEX),
26192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.getString(ACCOUNT_TYPE_INDEX));
26202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
26212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor != null) {
26222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                cursor.close();
26232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
26242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
26252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return account;
26262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
26272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
26289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
26299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
26369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
26379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
26389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
26459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
26469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
26509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
26519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
26549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
265824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param attendeeValues the column values for one row in the Attendees table.
26599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
26609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
26619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
266224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Long eventIdObj = attendeeValues.getAsLong(Attendees.EVENT_ID);
266324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (eventIdObj == null) {
266424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            Log.w(TAG, "Attendee update values don't include an event_id");
266524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            return;
266624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
266724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        long eventId = eventIdObj;
26689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
26799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2680f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2681f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2682f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
26839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
26899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
27019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2702f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2703f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2704f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
27059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
27069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
27099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
27119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
273224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // Select a default value for "status" based on the relationship.
27339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
273424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer relationObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
273524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (relationObj != null) {
273624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            int rel = relationObj;
27379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
274224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // If the status is specified, use that.
274324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer statusObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
274424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (statusObj != null) {
274524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            status = statusObj;
27469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
27499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2750b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2751b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
27529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2754d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
27551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Set the "hasAlarm" column in the database.
27561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
27571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param eventId The _id of the Event to update.
27581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param val The value to set it to (0 or 1).
27591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
27601c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private void setHasAlarm(long eventId, int val) {
27611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues values = new ContentValues();
27621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        values.put(Events.HAS_ALARM, val);
27631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int count = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
27641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                new String[] { String.valueOf(eventId) });
27651c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (count != 1) {
27661c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Log.w(TAG, "setHasAlarm on event " + eventId + " updated " + count +
27671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    " rows (expected 1)");
27681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
27691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
27701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
27711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
2772d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Calculates the "last date" of the event.  For a regular event this is the start time
2773d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * plus the duration.  For a recurring event this is the start date of the last event in
2774d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence, plus the duration.  The event recurs forever, this returns -1.  If
2775d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence rule can't be parsed, this returns -1.
2776d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2777d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values
2778d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the date, in milliseconds, since the start of the epoch (UTC), or -1 if an
2779d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   exceptional condition exists.
2780d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws DateException
2781d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
27829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
27849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
27879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
27889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
27899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
27909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
27919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
27929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
27939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
27949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
27969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
27989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
27999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
28019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
28029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
28039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
28049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
28059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
28069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
28079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
28089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
28099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
28109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
28119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
28129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
28139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2816f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2817f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2818f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2819f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2820f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2821f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2822b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            values.get(CalendarContract.Events.RRULE), e);
2823f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2824d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: this should throw an exception or return a distinct error code
2825f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2826f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2828f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
28369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
28419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
28439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
28449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
2845d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // repeats forever
28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
28499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
28509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
28519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
28549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
28559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
28579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
28589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2859e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2860e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
286182b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     * @param values the ContentValues (in/out); must include DTSTART and, if the event is
286282b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   recurring, the columns necessary to process a recurrence rule (RRULE, DURATION,
286382b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   EVENT_TIMEZONE, etc).
2864e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2865e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2866e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
28679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
28689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
28699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
28709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
28719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
28749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
28759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2876f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2877f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2878f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
28799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
28809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
28829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2883d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2884d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Creates or updates an entry in the EventsRawTimes table.
2885d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2886d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param eventId The ID of the event that was just created or is being updated.
2887d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values For a new event, the full set of event values; for an updated event,
2888d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   the set of values that are being changed.
2889d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
28909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
28919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
28929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2893b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        rawValues.put(CalendarContract.EventsRawTimes.EVENT_ID, eventId);
28949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
28969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
28989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
28999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
29009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
29049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
29059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
29069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
29099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
29119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
2913b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTSTART_2445, time.format2445());
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
2919b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTEND_2445, time.format2445());
29209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
29289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
29299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
29309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
2932b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445,
2933b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
29349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
29379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
29389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
2940b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.LAST_DATE_2445, time.format2445());
29419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
29449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2947b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2948b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2949ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
29509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
29519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
29530739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
29540739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
29550739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
29569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
29579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
29589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
29599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
29612ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
29629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
29639323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2964dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2965dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2966dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2967dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
29702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                return deleteMatchingColors(appendAccountToSelection(uri, selection),
29712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
29722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
29731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
29749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
29757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
29760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection = appendSyncAccountToSelection(uri, selection);
29777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
29781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
2979ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak                Cursor cursor = mDb.query(Views.EVENTS, ID_ONLY_PROJECTION,
29801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
29817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
29829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
29831ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
29841ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
298510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
29869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2987420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
2988dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
29899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
29909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
29919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
29929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
29949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
29961ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
29971ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
299810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
29991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
3000bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID2:
3001bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            {
3002bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // This will throw NumberFormatException on missing or malformed input.
3003bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                List<String> segments = uri.getPathSegments();
3004bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long eventId = Long.parseLong(segments.get(1));
3005bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long excepId = Long.parseLong(segments.get(2));
3006bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // TODO: verify that this is an exception instance (has an ORIGINAL_ID field
3007bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //       that matches the supplied eventId)
3008bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return deleteEventInternal(excepId, callerIsSyncAdapter, false /* isBatch */);
3009bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
30109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
30119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3013b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
30147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
30151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, selection,
30161c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
30177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
30209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
30227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3023b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
3024b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
30257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
30261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, null /* selection */,
30272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
30287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
30319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, false, selection, selectionArgs, callerIsSyncAdapter);
30339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
30359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30361c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, true, null /*selection*/, null /*selectionArgs*/,
30371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        callerIsSyncAdapter);
30382fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
30392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
30402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
30412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
3042b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
30432fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
30441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri, selection,
3045b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
30462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
30472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
30482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
30492fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
30502fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
30512fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
3052b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
3053636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
30542fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
30551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri,
30561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            null /* selection */, null /* selectionArgs */);
30577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
30609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3062b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
30637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
30641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.CALENDAR_ALERTS, uri, selection,
30651c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
30667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
30699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30702fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
30712fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
30729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3073b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
3074b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
30759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
30772ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                StringBuilder selectionSb = new StringBuilder(Calendars._ID + "=");
30789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
30799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
30809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
30819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
30829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
30839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
30849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
308524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                // $FALL-THROUGH$ - fall through to CALENDARS for the actual delete
30869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
3087595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
308874ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                return deleteMatchingCalendars(selection, selectionArgs);
30899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
30909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
30916db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
3092315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
30939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
30949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
30959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
30969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
309910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
31001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
3101192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
31021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
31031ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
3104b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
3105b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
3106636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
31071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
31081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
31091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
31101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
31111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
311248f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
31131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
31141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
31151ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
31161ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
31171ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
31181ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
3119b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origId = cursor.getString(EVENTS_ORIGINAL_ID_INDEX);
3120b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origSyncId = cursor.getString(EVENTS_ORIGINAL_SYNC_ID_INDEX);
3121b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                if (isRecurrenceEvent(rrule, rdate, origId, origSyncId)) {
31221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
31231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
31244d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                boolean isRecurrence = !TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate);
31251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
312648f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
312748f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
3128bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //
3129bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // The EVENTS_CLEANUP_TRIGGER_SQL trigger will remove all associated data
3130bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // (Attendees, Instances, Reminders, etc).
313148f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
3132b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
31334d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
31344d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // If this is a recurrence, and the event was never synced with the server,
31354d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we want to delete any exceptions as well.  (If it has been to the server,
31364d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we'll let the sync adapter delete the events explicitly.)  We assume that,
31374d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // if the recurrence hasn't been synced, the exceptions haven't either.
31384d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    if (isRecurrence && emptySyncId) {
31394d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                        mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID, selectionArgs);
31404d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    }
31411ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
3142bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // Event is on the server, so we "soft delete", i.e. mark as deleted so that
3143bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the sync adapter has a chance to tell the server about the deletion.  After
3144bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the server sees the change, the sync adapter will do the "hard delete"
3145bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // (above).
31461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
31471b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
3148c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
3149b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
315002494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
31514d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // Exceptions that have been synced shouldn't be deleted -- the sync
31524d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // adapter will take care of that -- but we want to "soft delete" them so
31534d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // that they will be removed from the instances list.
31544d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // TODO: this seems to confuse the sync adapter, and leaves you with an
31554d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       invisible "ghost" event after the server sync.  Maybe we can fix
31564d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       this by making instance generation smarter?  Not vital, since the
31574d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       exception instances disappear after the server sync.
31584d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //mDb.update(Tables.EVENTS, values, SQL_WHERE_ORIGINAL_ID_HAS_SYNC_ID,
31594d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //        selectionArgs);
31604d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
31614d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // It's possible for the original event to be on the server but have
31624d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // exceptions that aren't.  We want to remove all events with a matching
31634d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // original_id and an empty _sync_id.
31644d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID,
31654d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                            selectionArgs);
31664d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
316743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
316843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
3169b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
3170b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
3171b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
3172b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
3173b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
3174b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
31751ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
31761ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
31771ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
31781ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
31791ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
31801ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
31818f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
318210b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
3183420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3184dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
318510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
31861ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
31871ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
31881ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
31897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
31901c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Delete rows from an Event-related table (e.g. Attendees) and mark corresponding events
31911c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * as dirty.
31921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
31937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
31947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
31957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
31967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
31977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
31981c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteFromEventRelatedTable(String table, Uri uri, String selection,
31991c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            String[] selectionArgs) {
32001c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (table.equals(Tables.EVENTS)) {
32011c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            throw new IllegalArgumentException("Don't delete Events with this method "
32021c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    + "(use deleteEventInternal)");
32031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
32041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32051c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues dirtyValues = new ContentValues();
32061c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        dirtyValues.put(Events.DIRTY, "1");
32071c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32081c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32091c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Re-issue the delete URI as a query.  Note that, if this is a by-ID request, the ID
32101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * will be in the URI, not selection/selectionArgs.
32111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
32121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Note that the query will return data according to the access restrictions,
32131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * so we don't need to worry about deleting data we don't have permission to read.
32141c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
32151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, GENERIC_EVENT_ID);
32167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
32177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
32181c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long prevEventId = -1;
32191c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
32201c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long id = c.getLong(ID_INDEX);
32211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = c.getLong(EVENT_ID_INDEX);
32221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // Duplicate the event.  As a minor optimization, don't try to duplicate an
32231c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // event that we just duplicated on the previous iteration.
32241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
32251c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventId);
32261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    prevEventId = eventId;
32271c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
32289ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDb.delete(table, SQL_WHERE_ID, new String[]{String.valueOf(id)});
32291c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
32301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
32311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            new String[] { String.valueOf(eventId)} );
32321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
32337e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
32347e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
32357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
32367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
32377e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
32387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
32397e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
32407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
32417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
32421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Deletes rows from the Reminders table and marks the corresponding events as dirty.
32431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Ensures the hasAlarm column in the Event is updated.
32441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
32451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @return The number of rows deleted.
32461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
32471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteReminders(Uri uri, boolean byId, String selection, String[] selectionArgs,
32481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            boolean callerIsSyncAdapter) {
32491c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32501c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this is a by-ID URI, make sure we have a good ID.  Also, confirm that the
32511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * selection is null, since we will be ignoring it.
32521c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
32531c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        long rowId = -1;
32541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
32551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (!TextUtils.isEmpty(selection)) {
32561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
32571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
32581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            rowId = ContentUris.parseId(uri);
32591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (rowId < 0) {
32601c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
32611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
32621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
32631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32651c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Determine the set of events affected by this operation.  There can be multiple
32661c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders with the same event_id, so to avoid beating up the database with "how many
32671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders are left" and "duplicate this event" requests, we want to generate a list
32681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * of affected event IDs and work off that.
32691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
32701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * TODO: use GROUP BY to reduce the number of rows returned in the cursor.  (The content
32711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * provider query() doesn't take it as an argument.)
32721c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
32731c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        HashSet<Long> eventIdSet = new HashSet<Long>();
32741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, new String[] { Attendees.EVENT_ID }, selection, selectionArgs, null);
32751c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        try {
32761c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
32771c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                eventIdSet.add(c.getLong(0));
32781c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
32791c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        } finally {
32801c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            c.close();
32811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
32821c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32831c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32841c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this isn't a sync adapter, duplicate each event (along with its associated tables),
32851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * and mark each as "dirty".  This is for the benefit of partial-update sync.
32861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
32871c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (!callerIsSyncAdapter) {
32881c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            ContentValues dirtyValues = new ContentValues();
32891c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            dirtyValues.put(Events.DIRTY, "1");
32901c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32911c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Iterator<Long> iter = eventIdSet.iterator();
32921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (iter.hasNext()) {
32931c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = iter.next();
32941c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDbHelper.duplicateEvent(eventId);
32951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
32961c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
32971c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
32981c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
32991c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33001c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33011c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Issue the original deletion request.  If we were called with a by-ID URI, generate
33021c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * a selection.
33031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
33051c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selection = SQL_WHERE_ID;
33061c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
33071c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33081c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int delCount = mDb.delete(Tables.REMINDERS, selection, selectionArgs);
33091c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * For each event, set "hasAlarm" to zero if we've deleted the last of the reminders.
33121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * (If the event still has reminders, hasAlarm should already be 1.)  Because we're
33131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * executing in an exclusive transaction there's no risk of racing against other
33141c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * database updates.
33151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33161c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues noAlarmValues = new ContentValues();
33171c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        noAlarmValues.put(Events.HAS_ALARM, 0);
33181c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Iterator<Long> iter = eventIdSet.iterator();
33191c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        while (iter.hasNext()) {
33201c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long eventId = iter.next();
33211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            // Count up the number of reminders still associated with this event.
33231c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Cursor reminders = mDb.query(Tables.REMINDERS, new String[] { GENERIC_ID },
33241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    SQL_WHERE_EVENT_ID, new String[] { String.valueOf(eventId) },
33251c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    null, null, null);
33261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            int reminderCount = reminders.getCount();
33271c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            reminders.close();
33281c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33291c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (reminderCount == 0) {
33301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, noAlarmValues, SQL_WHERE_ID,
33311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
33321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33331c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33341c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33351c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        return delCount;
33361c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
33371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
333924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * Update rows in a table and, if this is a non-sync-adapter update, mark the corresponding
334024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * events as dirty.
334124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * <p>
334224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * This only works for tables that are associated with an event.  It is assumed that the
334324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * link to the Event row is a numeric identifier in a column called "event_id".
334424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     *
334524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param uri The original request URI.
334624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param byId Set to true if the URI is expected to include an ID.
334724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param updateValues The new values to apply.  Not all columns need be represented.
334824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selection For non-by-ID operations, the "where" clause to use.
334924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selectionArgs For non-by-ID operations, arguments to apply to the "where" clause.
335024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param callerIsSyncAdapter Set to true if the caller is a sync adapter.
335124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @return The number of rows updated.
33527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
335324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private int updateEventRelatedTable(Uri uri, String table, boolean byId,
335424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues updateValues, String selection, String[] selectionArgs,
335524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            boolean callerIsSyncAdapter)
335624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    {
335724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
335824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Confirm that the request has either an ID or a selection, but not both.  It's not
335924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * actually "wrong" to have both, but it's not useful, and having neither is likely
336024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * a mistake.
336124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         *
336224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * If they provided an ID in the URI, convert it to an ID selection.
336324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
336424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (byId) {
336524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!TextUtils.isEmpty(selection)) {
336624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
336724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
336824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            long rowId = ContentUris.parseId(uri);
336924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (rowId < 0) {
337024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
337124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
337224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selection = SQL_WHERE_ID;
337324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
337424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        } else {
337524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (TextUtils.isEmpty(selection)) {
337624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection is required for " + uri);
337724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
337824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
337924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
338024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
338124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Query the events to update.  We want all the columns from the table, so we us a
338224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * null projection.
338324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
338424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Cursor c = mDb.query(table, null /*projection*/, selection, selectionArgs,
338524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                null, null, null);
33867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
33877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
338824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (c.getCount() == 0) {
338924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                Log.d(TAG, "No query results for " + uri + ", selection=" + selection +
339024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        " selectionArgs=" + Arrays.toString(selectionArgs));
339124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return 0;
339224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
339324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
339424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues dirtyValues = null;
339524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!callerIsSyncAdapter) {
339624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues = new ContentValues();
339724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues.put(Events.DIRTY, "1");
339824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
339924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
340024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int idIndex = c.getColumnIndex(GENERIC_ID);
340124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int eventIdIndex = c.getColumnIndex(GENERIC_EVENT_ID);
340224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (idIndex < 0 || eventIdIndex < 0) {
340324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new RuntimeException("Lookup on _id/event_id failed for " + uri);
340424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
340524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
340624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            /*
340724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * For each row found:
340824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - merge original values with update values
340924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update database
341024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - if not sync adapter, set "dirty" flag in corresponding event to 1
341124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update Event attendee status
341224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             */
341324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            while (c.moveToNext()) {
341424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /* copy the original values into a ContentValues, then merge the changes in */
341524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                ContentValues values = new ContentValues();
341624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                DatabaseUtils.cursorRowToContentValues(c, values);
341724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                values.putAll(updateValues);
341824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
341924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long id = c.getLong(idIndex);
342024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long eventId = c.getLong(eventIdIndex);
342124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
342224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    // Make a copy of the original, so partial-update code can see diff.
342324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDbHelper.duplicateEvent(eventId);
342424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
342524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                mDb.update(table, values, SQL_WHERE_ID, new String[] { String.valueOf(id) });
342624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
342724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
342824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                            new String[] { String.valueOf(eventId) });
342924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
34307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
343124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
343224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /*
343324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * The Events table has a "selfAttendeeStatus" field that usually mirrors the
343424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * "attendeeStatus" column of one row in the Attendees table.  It's the provider's
343524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * job to keep these in sync, so we have to check for changes here.  (We have
343624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * to do it way down here because this is the only point where we have the
343724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * merged Attendees values.)
343824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
343924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * It's possible, but not expected, to have multiple Attendees entries with
344024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * matching attendeeEmail.  The behavior in this case is not defined.
344124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
344224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * We could do this more efficiently for "bulk" updates by caching the Calendar
344324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * owner email and checking it here.
344424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 */
344524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (table.equals(Tables.ATTENDEES)) {
344624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    updateEventAttendeeStatus(mDb, values);
344724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
34487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
34497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
34507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
34517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
34527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
34537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
34547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
34552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int deleteMatchingColors(String selection, String[] selectionArgs) {
34562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // query to find all the colors that match, for each
34572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - verify no one references it
34582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - delete color
34592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs, null,
34602f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                null, null);
34612f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (c == null) {
34622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            return 0;
34632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
34642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
34652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            Cursor c2 = null;
34662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            while (c.moveToNext()) {
34672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String index = c.getString(COLORS_COLOR_INDEX_INDEX);
34682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = c.getString(COLORS_ACCOUNT_NAME_INDEX);
34692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = c.getString(COLORS_ACCOUNT_TYPE_INDEX);
34702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                boolean isCalendarColor = c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
34712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
34722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (isCalendarColor) {
34732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = mDb.query(Tables.CALENDARS, ID_ONLY_PROJECTION,
34742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                SQL_WHERE_CALENDAR_COLOR, new String[] {
34752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                        accountName, accountType, index
34762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                }, null, null, null);
34772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
34782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
34792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " calendars.");
34802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
34812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
34822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
34832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = query(Events.CONTENT_URI, ID_ONLY_PROJECTION, SQL_WHERE_EVENT_COLOR,
34842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                new String[] {accountName, accountType, index}, null);
34852f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
34862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
34872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " events.");
34882f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
34892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
34902f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
34912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
34922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c2 != null) {
34932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2.close();
34942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
34952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
34962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
34972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
34982f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
34992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
35002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
35012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
35022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return mDb.delete(Tables.COLORS, selection, selectionArgs);
35032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
35042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
350574ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio    private int deleteMatchingCalendars(String selection, String[] selectionArgs) {
35069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
35079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
35089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
350974ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, selection,
351074ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                selectionArgs,
351174ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* groupBy */,
351274ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* having */,
351374ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* sortOrder */);
35149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
35159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
35169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
35189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
35199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
35209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
35219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
35229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
35239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
35249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
352574ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        return mDb.delete(Tables.CALENDARS, selection, selectionArgs);
35269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3528fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
3529fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
3530fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3531fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
3532fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3533fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
3534fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3535fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
3536fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
3537fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
3538fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3539fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3540fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
3541fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
3542fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3543fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
3544fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
3545fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
3546fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3547fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
3548fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
3549fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
3550d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean doesStatusCancelUpdateMeanUpdate(ContentValues values,
3551d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues modValues) {
3552d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean isStatusCanceled = modValues.containsKey(Events.STATUS) &&
3553d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                (modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
3554fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
3555d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID);
3556d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3557d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!TextUtils.isEmpty(originalSyncId)) {
3558d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // This event is an exception.  See if the recurring event still exists.
3559d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                return doesEventExistForSyncId(originalSyncId);
3560d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3561d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3562d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // This is the normal case, we just want an UPDATE
3563d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return true;
3564d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
3565d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
35662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int handleUpdateColors(ContentValues values, String selection, String[] selectionArgs) {
35672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
35682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int result = mDb.update(Tables.COLORS, values, selection, selectionArgs);
35692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (values.containsKey(Colors.COLOR)) {
35702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            try {
35712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs,
35722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        null /* groupBy */, null /* having */, null /* orderBy */);
35732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                while (c.moveToNext()) {
35742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    boolean calendarColor =
35752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
35762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = c.getInt(COLORS_COLOR_INDEX);
35772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String[] args = {
35782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_NAME_INDEX),
35792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_TYPE_INDEX),
35802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_COLOR_INDEX_INDEX)
35812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    };
35822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ContentValues colorValue = new ContentValues();
35832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (calendarColor) {
35842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Calendars.CALENDAR_COLOR, color);
3585d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.CALENDARS, colorValue, SQL_WHERE_CALENDAR_COLOR, args);
35862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
35872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Events.EVENT_COLOR, color);
3588d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.EVENTS, colorValue, SQL_WHERE_EVENT_COLOR, args);
35892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
35902f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
35912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            } finally {
35922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (c != null) {
35932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    c.close();
35942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
35952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
35962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
35972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return result;
35982f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
35992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
3600d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3601d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
3602d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Handles a request to update one or more events.
3603d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
3604d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * The original event(s) will be loaded from the database, merged with the new values,
3605d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * and the result checked for validity.  In some cases this will alter the supplied
3606d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * arguments (e.g. zeroing out the times on all-day events), change additional fields (e.g.
3607d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * update LAST_DATE when DTSTART changes), or cause modifications to other tables (e.g. reset
3608d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Instances when a recurrence rule changes).
3609d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
3610d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param cursor The set of events to update.
36114b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden     * @param updateValues The changes to apply to each event.
3612d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param callerIsSyncAdapter Indicates if the request comes from the sync adapter.
3613d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the number of rows updated
3614d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
36154b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden    private int handleUpdateEvents(Cursor cursor, ContentValues updateValues,
3616d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean callerIsSyncAdapter) {
3617d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        /*
36181c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * This field is considered read-only.  It should not be modified by applications or
36191c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * by the sync adapter.
36201c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
36211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        updateValues.remove(Events.HAS_ALARM);
36221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
36231c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
3624d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For a single event, we can just load the event, merge modValues in, perform any
3625d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * fix-ups (putting changes into modValues), check validity, and then update().  We have
3626d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * to be careful that our fix-ups don't confuse the sync adapter.
3627d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         *
3628d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For multiple events, we need to load, merge, and validate each event individually.
3629d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * If no single-event-specific changes need to be made, we could just issue the original
3630d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * bulk update, which would be more efficient than a series of individual updates.
3631d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * However, doing so would prevent us from taking advantage of the partial-update
3632d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * mechanism.
3633d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         */
3634d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (cursor.getCount() > 1) {
3635d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (Log.isLoggable(TAG, Log.DEBUG)) {
3636d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG, "Performing update on " + cursor.getCount() + " events");
3637d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3638d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3639d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        while (cursor.moveToNext()) {
36409f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Make a copy of updateValues so we can make some local changes.
36414b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden            ContentValues modValues = new ContentValues(updateValues);
36429f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
36439f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Load the event into a ContentValues object.
3644d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues values = new ContentValues();
3645d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
36469f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            boolean doValidate = false;
36479f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (!callerIsSyncAdapter) {
36489f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                try {
36499f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // Check to see if the data in the database is valid.  If not, we will skip
36509f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // validation of the update, so that we don't blow up on attempts to
36519f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // modify existing badly-formed events.
36529f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    validateEventData(values);
36539f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    doValidate = true;
36549f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                } catch (IllegalArgumentException iae) {
36559f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    Log.d(TAG, "Event " + values.getAsString(Events._ID) +
36569f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            " malformed, not validating update (" +
36579f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            iae.getMessage() + ")");
36589f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                }
36599f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
36609f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
36619f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Merge the modifications in.
3662d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            values.putAll(modValues);
3663d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
36642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If a color_index is being set make sure it's valid
3665387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_id = modValues.getAsString(Events.EVENT_COLOR_KEY);
36662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_id)) {
36672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
36682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
36692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = mDb.query(Tables.CALENDARS, ACCOUNT_PROJECTION, SQL_WHERE_ID,
36702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        new String[] { values.getAsString(Events.CALENDAR_ID) }, null, null, null);
36712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
36722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.moveToFirst()) {
36732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = c.getString(ACCOUNT_NAME_INDEX);
36742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = c.getString(ACCOUNT_TYPE_INDEX);
36752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
36762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
36772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null) {
36782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
36792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
36802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
36812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_id, Colors.TYPE_EVENT);
36822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
36832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
36849f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Scrub and/or validate the combined event.
3685be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            if (callerIsSyncAdapter) {
3686be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                scrubEventData(values, modValues);
36879f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
36889f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (doValidate) {
3689be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                validateEventData(values);
3690be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            }
3691d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3692d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Look for any updates that could affect LAST_DATE.  It's defined as the end of
3693d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // the last meeting, so we need to pay attention to DURATION.
3694d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.DTSTART) ||
3695d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DTEND) ||
3696d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DURATION) ||
3697d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EVENT_TIMEZONE) ||
3698d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RRULE) ||
3699d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RDATE) ||
3700d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXRULE) ||
3701d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXDATE)) {
3702d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long newLastDate;
3703d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3704d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    newLastDate = calculateLastDate(values);
3705d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } catch (DateException de) {
3706d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    throw new IllegalArgumentException("Unable to compute LAST_DATE", de);
3707d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3708d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Long oldLastDateObj = values.getAsLong(Events.LAST_DATE);
3709d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long oldLastDate = (oldLastDateObj == null) ? -1 : oldLastDateObj;
3710d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (oldLastDate != newLastDate) {
3711d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // This overwrites any caller-supplied LAST_DATE.  This is okay, because the
3712d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // caller isn't supposed to be messing with the LAST_DATE field.
3713d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (newLastDate < 0) {
3714d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.putNull(Events.LAST_DATE);
3715d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    } else {
3716d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.put(Events.LAST_DATE, newLastDate);
3717fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3718fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3719d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3720d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3721d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!callerIsSyncAdapter) {
3722d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DIRTY, 1);
3723d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3724fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3725d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Disallow updating the attendee status in the Events
3726d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // table.  In the future, we could support this but we
3727d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // would have to query and update the attendees table
3728d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // to keep the values consistent.
3729d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
3730d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Updating "
3731d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + Events.SELF_ATTENDEE_STATUS
3732d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + " in Events table is not allowed.");
3733d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3734d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3735d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (fixAllDayTime(values, modValues)) {
3736d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (Log.isLoggable(TAG, Log.WARN)) {
3737d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    Log.w(TAG, "handleUpdateEvents: " +
3738d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            "allDay is true but sec, min, hour were not 0.");
3739fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3740d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3741d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3742d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // For taking care about recurrences exceptions cancelations, check if this needs
3743d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            //  to be an UPDATE or a DELETE
3744d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean isUpdate = doesStatusCancelUpdateMeanUpdate(values, modValues);
3745d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3746d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            long id = values.getAsLong(Events._ID);
3747d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3748d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (isUpdate) {
3749d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // If a user made a change, possibly duplicate the event so we can do a partial
3750d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // update. If a sync adapter made a change and that change marks an event as
3751d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // un-dirty, remove any duplicates that may have been created earlier.
3752d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (!callerIsSyncAdapter) {
3753d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mDbHelper.duplicateEvent(id);
3754d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } else {
3755d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DIRTY)
3756d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            && modValues.getAsInteger(Events.DIRTY) == 0) {
3757d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mDbHelper.removeDuplicateEvent(id);
3758d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3759d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3760d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int result = mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
3761d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        new String[] { String.valueOf(id) });
3762d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (result > 0) {
3763d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    updateEventRawTimesLocked(id, modValues);
3764d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mInstancesHelper.updateInstancesLocked(modValues, id,
3765d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            false /* not a new event */, mDb);
3766d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3767d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // XXX: should we also be doing this when RRULE changes (e.g. instances
3768d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    //      are introduced or removed?)
3769d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DTSTART) ||
3770d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            modValues.containsKey(Events.STATUS)) {
3771d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // If this is a cancellation knock it out
3772d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // of the instances table
3773d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (modValues.containsKey(Events.STATUS) &&
3774d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED) {
3775d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            String[] args = new String[] {String.valueOf(id)};
3776d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, args);
3777d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3778d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3779d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // The start time or status of the event changed, so run the
3780d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // event alarm scheduler.
3781d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (Log.isLoggable(TAG, Log.DEBUG)) {
3782d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            Log.d(TAG, "updateInternal() changing event");
3783d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3784d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3785d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3786d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3787d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    sendUpdateNotification(id, callerIsSyncAdapter);
3788d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3789d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else {
3790d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3791d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3792d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                sendUpdateNotification(callerIsSyncAdapter);
3793fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3794fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3795d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3796d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return cursor.getCount();
3797fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3798fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
37999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
38009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
3801b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
3802ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
38039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
38049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
38060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
38070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
38089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
38109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
38119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
38129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
38139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
38159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
38162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
3817dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
38189323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3819dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3820dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3821dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
38229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
38252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Integer color = values.getAsInteger(Colors.COLOR);
38262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (values.size() > 1 || (values.size() == 1 && color == null)) {
38272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new UnsupportedOperationException("You may only change the COLOR "
38282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            + "for an existing Colors entry.");
38292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
38302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                return handleUpdateColors(values, appendAccountToSelection(uri, selection),
38312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
38322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
383343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
38349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
38359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
383643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
383743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
383843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
383943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
384043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
384143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
384243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
384343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
384443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
38454cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    if (selection != null && TextUtils.equals(selection,"_id=?")) {
38464cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                        id = Long.parseLong(selectionArgs[0]);
38474cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
384843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
384943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
385043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
385143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
3852b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
385343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
385443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
385543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
3856c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Calendars.DIRTY, 1);
38572fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
38589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
38599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
38609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
38619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3862387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
38632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
38642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
38652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
38662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
38672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        Account account = getAccount(id);
38682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (account != null) {
38692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountName = account.name;
38702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountType = account.type;
38712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
38722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
38732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    verifyColorExists(accountName, accountType, color_id, Colors.TYPE_CALENDAR);
38742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
38759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3876b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
3877636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
38789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38793ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
3880d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
38814067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                    if (values.containsKey(Calendars.VISIBLE)) {
3882d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
3883d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
3884d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
3885d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
3886420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        mCalendarAlarm.scheduleNextAlarm(false);
3887d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
38883ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
3889dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
38903ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
38913ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
38929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
38939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
38959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
38969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
3897d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Cursor events = null;
38989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3899d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // Grab the full set of columns for each selected event.
3900d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: define a projection with just the data we need (e.g. we don't need to
3901d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                //       validate the SYNC_* columns)
39029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3903d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3904d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (match == EVENTS_ID) {
3905d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // Single event, identified by ID.
3906d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        long id = ContentUris.parseId(uri);
3907d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3908d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                SQL_WHERE_ID, new String[] { String.valueOf(id) },
3909d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
39109ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    } else {
3911d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // One or more events, identified by the selection / selectionArgs.
3912d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3913d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                selection, selectionArgs,
3914d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
39159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    }
391606c305d35741db303bd3aacd0eab5af8de0ab34eErik
3917d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events.getCount() == 0) {
391824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        Log.i(TAG, "No events to update: uri=" + uri + " selection=" + selection +
3919d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                " selectionArgs=" + Arrays.toString(selectionArgs));
3920d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        return 0;
3921d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
39223ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3923d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    return handleUpdateEvents(events, values, callerIsSyncAdapter);
3924d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } finally {
3925d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events != null) {
3926d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events.close();
3927fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
39289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
39299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
393024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES:
393124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, false, values, selection,
393224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
393324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES_ID:
393424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, true, values, null, null,
393524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
39369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39372fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
39382fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
39392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
39409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3941b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
3942636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
39439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39442fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
39452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
39462fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
3947b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
39489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
394924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
395024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case REMINDERS:
395124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.REMINDERS, false, values, selection,
395224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
39532fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
395424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                int count = updateEventRelatedTable(uri, Tables.REMINDERS, true, values, null, null,
395524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
39567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
39579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
39589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
39599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
39609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
39619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3962420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
39637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
39649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
396524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
396624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case EXTENDED_PROPERTIES_ID:
396724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.EXTENDED_PROPERTIES, true, values,
396824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        null, null, callerIsSyncAdapter);
396924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
397083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
397183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
397283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
3973420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false);
397483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
397583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
397683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
3977420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(true);
397883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
397983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
39809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3981315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
3982315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
3983315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
3984315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3985315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3986315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
3987315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3988315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
3989315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
3990315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
3991315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3992315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3993315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
3994315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
3995315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3996315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
3997315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
3998b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
3999315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4000315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
4001f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "home", set the Instances
4002f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the previous
4003f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "auto", set the Instances
4004f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the current
4005f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // device one
4006f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone Instances is set AND if we are in "home"
4007f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone type, then save the timezone Instance into
4008f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // "previous" too
4009315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
4010315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
4011315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
4012315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
4013315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
4014315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
4015315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
4016315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
4017315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
4018315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
4019315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
4020315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4021315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
4022d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
4023315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
4024315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4025d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4026315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4027315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4028315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
4029315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
4030315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
4031315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
4032315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
4033315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4034d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4035315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4036315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4037315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4038315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4039315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
4040315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
4041315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
4042315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
4043315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
4044315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
4045315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
4046315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
4047d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
4048315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
4049315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
4050315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
4051d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
4052315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4053315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4054315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4055315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4056315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
4057315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
4058315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
40599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
40609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
40619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
40629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
40639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    /**
40652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * Verifies that a color with the given index exists for the given Calendar
40662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * entry.
40672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     *
40682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountName The email of the account the color is for
40692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountType The type of account the color is for
40704755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorIndex The color_index being set for the calendar
40714755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorType The type of color expected (Calendar/Event)
40722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @return The color specified by the index
40732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     */
40744755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private int verifyColorExists(String accountName, String accountType, String colorIndex,
40754755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            int colorType) {
40762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
40772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            throw new IllegalArgumentException("Cannot set color. A valid account does"
40782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    + " not exist for this calendar.");
40792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
40802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int color;
40812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
40822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
40834755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
40844755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            if (!c.moveToFirst()) {
40854755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                throw new IllegalArgumentException("Color type: " + colorType + " and index "
40864755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        + colorIndex + " does not exist for account.");
40872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
40882f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            color = c.getInt(COLORS_COLOR_INDEX);
40892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
40902f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
40912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
40922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
40932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
40942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return color;
40952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
40962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
40979ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendAccountFromParameterToSelection(String selection, Uri uri) {
4098b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
4099b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
4100b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
4101b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
4102595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
41039ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            final StringBuilder sb = new StringBuilder();
41049ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(Calendars.ACCOUNT_NAME + "=")
41059ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountName))
41069ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" AND ")
41079ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(Calendars.ACCOUNT_TYPE)
41089ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" = ")
41099ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountType));
41109ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(sb, selection);
4111595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
41129ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
41139ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
41149ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
41159ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
41169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendLastSyncedColumnToSelection(String selection, Uri uri) {
41179ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (getIsCallerSyncAdapter(uri)) {
41189ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
4119595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
41209ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final StringBuilder sb = new StringBuilder();
4121b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sb.append(CalendarContract.Events.LAST_SYNCED).append(" = 0");
41229ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return appendSelection(sb, selection);
4123595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
4124595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
41259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
4126b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
4127b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
4128b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
4129b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
41309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
4131b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Calendars.ACCOUNT_NAME
4132b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + "=" + DatabaseUtils.sqlEscapeString(accountName) + " AND "
4133b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Calendars.ACCOUNT_TYPE + "="
41340739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountType));
41359ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
41360739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        } else {
41370739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return selection;
41380739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
41390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
41400739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
41410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private String appendSyncAccountToSelection(Uri uri, String selection) {
41420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountName = QueryParameterUtils.getQueryParameter(uri,
4143b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
41440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountType = QueryParameterUtils.getQueryParameter(uri,
4145b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
41460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (!TextUtils.isEmpty(accountName)) {
4147b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Events.ACCOUNT_NAME + "="
41480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
4149b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Events.ACCOUNT_TYPE + "="
41509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
41519ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
41529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
41539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
41549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
41559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41579ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendSelection(StringBuilder sb, String selection) {
41589ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (!TextUtils.isEmpty(selection)) {
41599ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(" AND (");
41609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(selection);
41619ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(')');
41629ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
41639ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return sb.toString();
41649ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
41659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
41660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    /**
41670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * Verifies that the operation is allowed and throws an exception if it
41680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * isn't. This defines the limits of a sync adapter call vs an app call.
4169683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * <p>
4170683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * Also rejects calls that have a selection but shouldn't, or that don't have a selection
4171683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * but should.
4172c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik     *
41730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param type The type of call, {@link #TRANSACTION_QUERY},
41740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_INSERT}, {@link #TRANSACTION_UPDATE}, or
41750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_DELETE}
41760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param uri
41770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param values
41780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param isSyncAdapter
41790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     */
41800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyTransactionAllowed(int type, Uri uri, ContentValues values,
41810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            boolean isSyncAdapter, int uriMatch, String selection, String[] selectionArgs) {
4182f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Queries are never restricted to app- or sync-adapter-only, and we don't
4183f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // restrict the set of columns that may be accessed.
4184f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        if (type == TRANSACTION_QUERY) {
4185f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            return;
4186f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4187f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
4188683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        if (type == TRANSACTION_UPDATE || type == TRANSACTION_DELETE) {
41892f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // TODO review this list, document in contract.
4190683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            if (!TextUtils.isEmpty(selection)) {
4191683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Only allow selections for the URIs that can reasonably use them.
41922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Whitelist of URIs allowed selections
4193683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4194683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case SYNCSTATE:
4195683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDARS:
4196683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4197683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4198683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDAR_ALERTS:
4199683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4200683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EXTENDED_PROPERTIES:
4201683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
42022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    case COLORS:
4203683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4204683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4205683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection not permitted for " + uri);
4206683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4207683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            } else {
4208683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Disallow empty selections for some URIs.
42092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Blacklist of URIs _not_ allowed empty selections
4210683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4211683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4212683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4213683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4214683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
4215683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection must be specified for "
4216683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                                + uri);
4217683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4218683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4219683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4220683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            }
4221683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        }
4222683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden
4223f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Only the sync adapter can use these to make changes.
42242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (!isSyncAdapter) {
42252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            switch (uriMatch) {
42262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE:
42272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE_ID:
42282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES:
42292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES_ID:
42302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case COLORS:
42312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Only sync adapters may write using " + uri);
42322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                default:
42332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    break;
4234f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            }
4235f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4236f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
42370739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (type) {
42380739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_INSERT:
42390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42400739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException(
42410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                            "Inserting into instances not supported");
42420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4243c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4244c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
42450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42470739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
42480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
42490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
42500739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
42510739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42520739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
42530739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_UPDATE:
42540739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42550739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Updating instances not supported");
42560739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4257c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4258c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
42590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
42620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
42630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
42640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
42650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
42670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_DELETE:
42680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Deleting instances not supported");
42700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
42740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
42760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
42770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
42780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
42790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyHasAccount(Uri uri, String selection, String[] selectionArgs) {
4280c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String accountName = QueryParameterUtils.getQueryParameter(uri, Calendars.ACCOUNT_NAME);
42810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String accountType = QueryParameterUtils.getQueryParameter(uri,
4282c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Calendars.ACCOUNT_TYPE);
42830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
42840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (selection != null && selection.startsWith(ACCOUNT_SELECTION_PREFIX)) {
42850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountName = selectionArgs[0];
42860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountType = selectionArgs[1];
42870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
42880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
42890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
42900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            throw new IllegalArgumentException(
42910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    "Sync adapters must specify an account and account type: " + uri);
42920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
42930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
42940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
4295c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private void verifyColumns(ContentValues values, int uriMatch) {
4296c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
4297c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            return;
4298c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4299c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String[] columns;
4300c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        switch (uriMatch) {
4301c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4302c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4303c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4304c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4305c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = Events.PROVIDER_WRITABLE_COLUMNS;
4306c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4307c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            default:
4308c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = PROVIDER_WRITABLE_DEFAULT_COLUMNS;
4309c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4310c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4311c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
4312c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        for (int i = 0; i < columns.length; i++) {
4313c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            if (values.containsKey(columns[i])) {
4314c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                throw new IllegalArgumentException("Only the provider may write to " + columns[i]);
4315c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            }
4316c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4317c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    }
4318c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
43190739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyNoSyncColumns(ContentValues values, int uriMatch) {
4320c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
43210739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return;
43220739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43230739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String[] syncColumns;
43240739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (uriMatch) {
43250739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS:
43260739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS_ID:
43270739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES:
43280739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES_ID:
4329c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Calendars.SYNC_WRITABLE_COLUMNS;
4330c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4331c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4332c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4333c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4334c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4335c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Events.SYNC_WRITABLE_COLUMNS;
43360739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
43370739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            default:
43380739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                syncColumns = SYNC_WRITABLE_DEFAULT_COLUMNS;
43390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
43400739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
43410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        for (int i = 0; i < syncColumns.length; i++) {
43430739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (values.containsKey(syncColumns[i])) {
43440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                throw new IllegalArgumentException("Only sync adapters may write to "
43450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                        + syncColumns[i]);
43460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
43470739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
43490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
43509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
43519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
43529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
43539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
4354c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                new String[] {Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
4355fa332ecedc0c340109811552407142f6e4f600b2RoboErik                        Calendars.CAL_SYNC1, Calendars.SYNC_EVENTS},
43569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
43579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
43589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
43599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
43609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
43619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
43629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
4363ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
43649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
4365ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
4366ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
4367ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
4368ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
4369ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
4370ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
4371ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
43729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
43732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (cursor != null)
43742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.close();
43759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
43769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
43779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
43789535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
43799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
4380f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
4381f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
4382f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
4383f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
43849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
43859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
43869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
43879535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
43889535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
43899535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
43909535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
43919535627bf6295cd94447beb83e1aac41f50c3600Erik        }
43929535627bf6295cd94447beb83e1aac41f50c3600Erik
43939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
43949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
43959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
43969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
43979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
43989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
43999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
44009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
44019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
44029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4403a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4404a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4405a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4406a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
4407dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
44089ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4409a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4410dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
4411dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
4412dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
4413a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4414a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4415a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4416a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4417a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4418a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
4419a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
4420a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
4421a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
4422a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
4423a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
44249ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param eventId the ID of the event that changed, or -1 for no specific event
44259ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4426a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4427dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
4428dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
4429a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
4430a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
4431a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
4432a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
4433a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
4434dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
4435dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
4436dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
4437dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
4438dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
4439dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
4440dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
4441dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
4442dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
4443dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
4444dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
4445dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
4446dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
4447dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
4448dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
4449dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
4450dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
4451a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
4452dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
4453a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4454a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4455a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4456a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
4457a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
44589ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #sendUpdateNotification(boolean)} instead.
4459a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
44609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @see #sendUpdateNotification(boolean)
4461a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4462a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
4463a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
4464b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.CONTENT_URI);
4465f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
4466f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
4467f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
4468e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
4469a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4470a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
44710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_QUERY = 0;
44720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_INSERT = 1;
44730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_UPDATE = 2;
44740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_DELETE = 3;
44750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
44760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:off
44770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final String[] SYNC_WRITABLE_DEFAULT_COLUMNS = new String[] {
4478b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars.DIRTY,
4479b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars._SYNC_ID
44800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    };
4481c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String[] PROVIDER_WRITABLE_DEFAULT_COLUMNS = new String[] {
4482c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    };
44830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:on
44840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
44859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
44869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
44879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
44882ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS = 4;
44892ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS_ID = 5;
44902ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES = 6;
44912ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES_ID = 7;
44922ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS = 8;
44932ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS_ID = 9;
44942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES = 10;
44952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES_ID = 11;
44962ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS = 12;
44972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_ID = 13;
44982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_BY_INSTANCE = 14;
44992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_BY_DAY = 15;
45002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE = 16;
45012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE_ID = 17;
45022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES = 18;
45032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES_ID = 19;
45042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_DAYS = 20;
45052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM = 21;
45062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM_REMOVE = 22;
45072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int TIME = 23;
45082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES = 24;
45092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES_ID = 25;
45102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH = 26;
45112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH_BY_DAY = 27;
45122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int PROVIDER_PROPERTIES = 28;
4513bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID = 29;
4514bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID2 = 30;
45153b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static final int EMMA = 31;
45162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS = 32;
45179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
45199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
45202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final HashMap<String, String> sColorsProjectionMap;
4521f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final HashMap<String, String> sEventsProjectionMap;
452219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
45239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
45249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
45259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
4526315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
452739c65e5716e21e863d8de587d139dae85f99422fFred Quintana    private static final HashMap<String, String> sCountProjectionMap;
45289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
4530b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/when/*/*", INSTANCES);
4531b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
4532b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
4533b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/searchbyday/*/*/*",
453481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
4535b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
4536b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events", EVENTS);
4537b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events/#", EVENTS_ID);
4538b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities", EVENT_ENTITIES);
4539b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
4540b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS);
4541b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS_ID);
4542b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
4543b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
4544b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees", ATTENDEES);
4545b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees/#", ATTENDEES_ID);
4546b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders", REMINDERS);
4547b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders/#", REMINDERS_ID);
4548b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
4549b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties/#",
4550b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                EXTENDED_PROPERTIES_ID);
4551b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
4552b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
4553b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/by_instance",
4554b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
4555b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate", SYNCSTATE);
4556b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
4557b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, CalendarAlarmManager.SCHEDULE_ALARM_PATH,
4558420b7fb569773ae573fbe90c3a9c522d4c368863Erik                SCHEDULE_ALARM);
4559b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY,
4560b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
4561b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time/#", TIME);
4562b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time", TIME);
4563b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "properties", PROVIDER_PROPERTIES);
4564b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#", EXCEPTION_ID);
4565b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#/#", EXCEPTION_ID2);
45663b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        sUriMatcher.addURI(CalendarContract.AUTHORITY, "emma", EMMA);
45672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "colors", COLORS);
45689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
456939c65e5716e21e863d8de587d139dae85f99422fFred Quintana        /** Contains just BaseColumns._COUNT */
457039c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap = new HashMap<String, String>();
457139c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
457239c65e5716e21e863d8de587d139dae85f99422fFred Quintana
45732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap = new HashMap<String, String>();
45742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors._ID, Colors._ID);
45752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.DATA, Colors.DATA);
45762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_NAME, Colors.ACCOUNT_NAME);
45772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_TYPE, Colors.ACCOUNT_TYPE);
4578387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sColorsProjectionMap.put(Colors.COLOR_KEY, Colors.COLOR_KEY);
45792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR_TYPE, Colors.COLOR_TYPE);
45802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR, Colors.COLOR);
45812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
45829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
45839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
458402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_NAME, Events.ACCOUNT_NAME);
458502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_TYPE, Events.ACCOUNT_TYPE);
4586c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.TITLE, Events.TITLE);
4587c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4588c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4589c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.STATUS, Events.STATUS);
459002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4591387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR_KEY, Events.EVENT_COLOR_KEY);
4592c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4593c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTSTART, Events.DTSTART);
4594c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTEND, Events.DTEND);
4595c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4596c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4597c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DURATION, Events.DURATION);
4598c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4599c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4600c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4601c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4602c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, Events.HAS_EXTENDED_PROPERTIES);
4603c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RRULE, Events.RRULE);
4604c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RDATE, Events.RDATE);
4605c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
4606c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
4607c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
460834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4609c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
4610c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4611c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4612c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4613c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4614c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, Events.GUESTS_CAN_INVITE_OTHERS);
4615c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4616c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4617c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4618c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DELETED, Events.DELETED);
461902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
46209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4621e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
46221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
46231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
46241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
46259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
4626c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
4627387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR_KEY);
462802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CALENDAR_ACCESS_LEVEL);
4629c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
463002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
4631c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
463202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
463302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
46342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap
46352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                .put(Calendars.ALLOWED_ATTENDEE_TYPES, Calendars.ALLOWED_ATTENDEE_TYPES);
46362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_AVAILABILITY, Calendars.ALLOWED_AVAILABILITY);
463702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
463802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND, Calendars.CAN_ORGANIZER_RESPOND);
463902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
4640c339afc7df041ebfc5f4587f78cf38562aa23459Alon Albert        sEventsProjectionMap.put(Events.DISPLAY_COLOR, Events.DISPLAY_COLOR);
46419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4642982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
4643e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
4644e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
4645982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4646e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4647982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
4648c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events._ID, Events._ID);
464902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
465002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
465102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
465202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
465302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
465402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
46559ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
465602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
465702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
465802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
465902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
466002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
466102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
466202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
466302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
466402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
466502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
466602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
466702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
466802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
4669c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DIRTY, Events.DIRTY);
46709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
46719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
467246f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
4673c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.TITLE, Events.TITLE);
4674c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4675c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4676c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.STATUS, Events.STATUS);
467702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4678c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4679c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTSTART, Events.DTSTART);
4680c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTEND, Events.DTEND);
4681c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4682c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4683c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DURATION, Events.DURATION);
4684c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4685c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4686c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4687c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4688c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES,
4689c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.HAS_EXTENDED_PROPERTIES);
4690c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RRULE, Events.RRULE);
4691c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RDATE, Events.RDATE);
4692c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
4693c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
4694c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
469534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4696c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
4697c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.ORIGINAL_INSTANCE_TIME);
4698c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4699c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4700c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4701c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4702c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS,
4703c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.GUESTS_CAN_INVITE_OTHERS);
4704c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4705c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4706c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4707c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DELETED, Events.DELETED);
470819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
470919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
471002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
471102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
471202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
471302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
471402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
471502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
47169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
471702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
471802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
471902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
4720470aa5bc291ca33d51dda356f38ac2954026da9aAlon Albert        sEventEntitiesProjectionMap.put(Events.DIRTY, Events.DIRTY);
47219ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
4722fa332ecedc0c340109811552407142f6e4f600b2RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
472302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
472402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
472502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
472602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
472702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
472802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
472902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
473002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
473102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
473219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
47339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
47341b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
47359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
47369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
47379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
47389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
47399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
47409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
47419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
47429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
47439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
47459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
47469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
47479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
47489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
47499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
47509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
47519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
475202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
475302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
47549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
47569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
47579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
47589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
47599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
4760361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
4761361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
47629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
47649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
47659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
47669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
47679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
47689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
47699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
47709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4771315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4772315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4773315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4774315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4775315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
47769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
47779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
477864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
47799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
478064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * This is called by AccountManager when the set of accounts is updated.
478164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * <p>
478264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * We are overriding this since we need to delete from the
47839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
47847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
47857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
478664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     *
478764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * @param accounts The list of currently active accounts.
47889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4789f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    @Override
47909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
479164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        Thread thread = new AccountsUpdatedThread(accounts);
479264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        thread.start();
479364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
479464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
479564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private class AccountsUpdatedThread extends Thread {
479664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        private Account[] mAccounts;
479764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
479864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountsUpdatedThread(Account[] accounts) {
479964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            mAccounts = accounts;
480064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
480164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
480264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        @Override
480364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        public void run() {
480464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // The process could be killed while the thread runs.  Right now that isn't a problem,
480564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // because we'll just call removeStaleAccounts() again when the provider restarts, but
480664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // if we want to do additional actions we may need to use a service (e.g. start
480764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // EmptyService in onAccountsUpdated() and stop it when we finish here).
480864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
480964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
481064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            removeStaleAccounts(mAccounts);
481164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
481264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
481364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
481464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    /**
481564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * Makes sure there are no entries for accounts that no longer exist.
481664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     */
481764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void removeStaleAccounts(Account[] accounts) {
4818ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4819ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
4820ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
4821ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4822ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4823ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
48249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
482546f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
48269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
48279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
48289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
48299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
48309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
48322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
48339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
48349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            for (String table : new String[]{Tables.CALENDARS, Tables.COLORS}) {
4836ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
48379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
48382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.rawQuery("SELECT DISTINCT " +
48392ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_NAME +
48407cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
48412ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_TYPE +
48427cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
48439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
48444cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // ACCOUNT_TYPE_LOCAL is to store calendars not associated
48454cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // with a system account. Typically, a calendar must be
48464cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // associated with an account on the device or it will be
48474cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // deleted.
4848b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    if (c.getString(0) != null
4849b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && c.getString(1) != null
4850b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && !TextUtils.equals(c.getString(1),
4851b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                    CalendarContract.ACCOUNT_TYPE_LOCAL)) {
48529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
48539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
48549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
48559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
48569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
48579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
48589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
48592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = null;
48609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
48619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
4863f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
4864f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
4865f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
48669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
4867b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
48682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // This will be a no-op for accounts without a color palette.
48692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                mDb.execSQL(SQL_DELETE_FROM_COLORS, params);
48709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
48719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
48729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
48739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
48742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
48752f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
48762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
48779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
48789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
48793ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
48803ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
4881dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
48829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
48839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4884636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
4885636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
4886636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
4887636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
4888636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
4889636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
4890636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
4891636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
4892636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
4893636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
4894636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
4895636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
4896636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
4897636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
4898636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
4899636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
4900636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
4901636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
4902636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
49039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
4904