CalendarProvider2.java revision 0332925aa9db8c4826327edd85030a4791b7a8e6
19f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/*
29f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
39f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Copyright 2006, The Android Open Source Project
49f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
59f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Licensed under the Apache License, Version 2.0 (the "License");
69f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** you may not use this file except in compliance with the License.
79f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** You may obtain a copy of the License at
89f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
99f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**     http://www.apache.org/licenses/LICENSE-2.0
109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Unless required by applicable law or agreed to in writing, software
129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** distributed under the License is distributed on an "AS IS" BASIS,
139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** See the License for the specific language governing permissions and
149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** limitations under the License.
169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff*/
179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpackage com.android.providers.calendar;
199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
37a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
38a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
41b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
42b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Attendees;
43b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
44b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
452f251c778c06d21ed7693a70f4a1268ff929242eRoboErikimport android.provider.CalendarContract.Colors;
46b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
47b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
48b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
49b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.SyncState;
509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
511edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
52192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
55ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
578d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.calendarcommon.DateException;
588d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.calendarcommon.Duration;
598d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.calendarcommon.EventRecurrence;
608d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.calendarcommon.RecurrenceProcessor;
618d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.calendarcommon.RecurrenceSet;
628d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
638d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
648d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.google.android.collect.Sets;
658d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.google.common.annotations.VisibleForTesting;
668d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
673b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.io.File;
682ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErikimport java.lang.reflect.Array;
693b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.lang.reflect.Method;
709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
71ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFaddenimport java.util.Iterator;
75dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
76bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFaddenimport java.util.Set;
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
78dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
83b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik * is defined in {@link android.provider.CalendarContract}.
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
888bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
89d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    static final boolean DEBUG_INSTANCES = false;
909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
917be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
92c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String ACCOUNT_SELECTION_PREFIX = Calendars.ACCOUNT_NAME + "=? AND "
93c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            + Calendars.ACCOUNT_TYPE + "=?";
947be45683e367bd6897daf6444b03be938f8f5eaaErik
95f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final boolean PROFILE = false;
969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
978f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
1029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
1039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
105b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            Events.ORIGINAL_ID,
106c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            Events.ORIGINAL_SYNC_ID,
1079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1089ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
1099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
112b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_ID_INDEX = 3;
113b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_SYNC_ID_INDEX = 4;
1147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] COLORS_PROJECTION = new String[] {
1162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_NAME,
1172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_TYPE,
1182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR_TYPE,
119387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        Colors.COLOR_KEY,
1202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR,
1212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_NAME_INDEX = 0;
1232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_TYPE_INDEX = 1;
1242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_TYPE_INDEX = 2;
1252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX_INDEX = 3;
1262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX = 4;
1272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1284755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private static final String COLOR_FULL_SELECTION = Colors.ACCOUNT_NAME + "=? AND "
1294755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + Colors.ACCOUNT_TYPE + "=? AND " + Colors.COLOR_TYPE + "=? AND " + Colors.COLOR_KEY
1304755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + "=?";
1314755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan
1322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_NAME = Calendars.ACCOUNT_NAME;
1332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_TYPE = Calendars.ACCOUNT_TYPE;
1342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] ACCOUNT_PROJECTION = new String[] {
1352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_NAME,
1362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_TYPE,
1372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_NAME_INDEX = 0;
1392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_TYPE_INDEX = 1;
1402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    // many tables have _id and event_id; pick a representative version to use as our generic
1421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_ID = Attendees._ID;
1431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_EVENT_ID = Attendees.EVENT_ID;
1441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
1457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_ID,
1471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_EVENT_ID,
1487e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
153646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
154646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
155646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
156646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
157646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
158646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
159646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
160646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
161646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
162646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
163646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
164646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
165646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
166646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
167646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
168646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
174ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
177f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    private CalendarInstancesHelper mInstancesHelper;
1789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1798ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
180f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // Due to an issue in Calendar Server restricting the length of the name we
181f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // had to strip it down
1828ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1838ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1848ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1858ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1868ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1873443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
188b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + ", " +
189b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTSTART_2445 + ", " +
190b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTEND_2445 + ", " +
1913443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1923443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
193b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
194b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1953443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
196b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
197b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_UPDATE_EVENT_SET_DIRTY = "UPDATE " +
199b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
200c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            " SET " + Events.DIRTY + "=1" +
201b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
202b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_WHERE_CALENDAR_COLOR = Calendars.ACCOUNT_NAME + "=? AND "
204387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            + Calendars.ACCOUNT_TYPE + "=? AND " + Calendars.CALENDAR_COLOR_KEY + "=?";
2052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
2062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_WHERE_EVENT_COLOR = Events.ACCOUNT_NAME + "=? AND "
207387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            + Events.ACCOUNT_TYPE + "=? AND " + Events.EVENT_COLOR_KEY + "=?";
2082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
20924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    protected static final String SQL_WHERE_ID = GENERIC_ID + "=?";
21024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private static final String SQL_WHERE_EVENT_ID = GENERIC_EVENT_ID + "=?";
2114d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID = Events.ORIGINAL_ID + "=?";
2124d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID = Events.ORIGINAL_ID +
2134d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden            "=? AND " + Events._SYNC_ID + " IS NULL";
214ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
215ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan    private static final String SQL_WHERE_ATTENDEE_BASE =
216ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID
217ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            + " AND " +
218ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
219ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
220b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
221ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " + SQL_WHERE_ATTENDEE_BASE;
222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
223b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
224b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
225ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID +
226ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            " AND " +
227ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
228b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
229b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
2302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
231b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
232b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
233b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
2342ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
235b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
236b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
237b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
238b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
239b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
240b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            Tables.EXTENDED_PROPERTIES + "." + CalendarContract.ExtendedProperties._ID + "=?";
241b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
242b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
2432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                " WHERE " + Calendars.ACCOUNT_NAME + "=? AND " +
2442ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    Calendars.ACCOUNT_TYPE + "=?";
245b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_DELETE_FROM_COLORS = "DELETE FROM " + Tables.COLORS + " WHERE "
2472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            + Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?";
2482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
249fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
250fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
251fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
26181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
26281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
26381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
26481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
265b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
26681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
267b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
26881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
26918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
27018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
27118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
27218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
27318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
274b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
27518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
276b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")" + ") LEFT OUTER JOIN " +
27718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
27818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
279b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Attendees.EVENT_ID + "=" +
28018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
281b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
28218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
283b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
284b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.START_DAY + "<=? AND " +
285b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END_DAY + ">=?";
28681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
287b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
288b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.BEGIN + "<=? AND " +
289b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END + ">=?";
2909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
2982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * The sort order is: events with an earlier start time occur first and if
2992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the start times are the same, then events with a later end time occur
3002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * first. The later end time is ordered first so that long-running events in
3012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the calendar views appear first. If the start and end times of two events
3022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * are the same then we sort alphabetically on the title. This isn't
3032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * required for correctness, it just adds a nice touch.
3042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
3052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
3062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
3072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
3082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * A regex for describing how we split search queries into tokens. Keeps
3092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * quoted phrases as one token. "one \"two three\"" ==> ["one" "two three"]
310dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
311dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
312dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
313dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
314dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
315dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
316dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
317dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
318dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
319dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
32081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
321dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
322dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
323dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
324dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
325dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
326dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
327dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
328dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
329dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
33081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
33118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
33218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
33318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
33418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
33518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
336b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_EMAIL + ")";
33718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
33818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
33918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
34018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
34118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
34218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
343b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_NAME + ")";
34418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
34581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
346b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.TITLE,
347b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.DESCRIPTION,
348b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.EVENT_LOCATION,
34918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
35018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
35181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
35281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
353a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
354a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
355a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
356a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
357a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
358a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
359a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
360a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
361a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
362a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
363a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
364a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
365dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
366dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
367dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
368dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
369dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
3708d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private static final HashSet<String> ALLOWED_URI_PARAMETERS = Sets.newHashSet(
3718d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.CALLER_IS_SYNCADAPTER,
3728d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.EventsEntity.ACCOUNT_NAME,
3738d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.EventsEntity.ACCOUNT_TYPE);
3748d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
375bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Set of columns allowed to be altered when creating an exception to a recurring event. */
376bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final HashSet<String> ALLOWED_IN_EXCEPTION = new HashSet<String>();
377bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    static {
378bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // _id, _sync_account, _sync_account_type, dirty, _sync_mark, calendar_id
379bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events._SYNC_ID);
380bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA1);
381bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA7);
38202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA3);
383bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.TITLE);
384bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_LOCATION);
385bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DESCRIPTION);
3862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR);
387387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR_KEY);
388bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.STATUS);
389c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SELF_ATTENDEE_STATUS);
39002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA6);
391bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DTSTART);
392c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // dtend -- set from duration as part of creating the exception
393bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_TIMEZONE);
394bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_END_TIMEZONE);
395bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DURATION);
396bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ALL_DAY);
397bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ACCESS_LEVEL);
398bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.AVAILABILITY);
399bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ALARM);
400bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_EXTENDED_PROPERTIES);
401bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RRULE);
402bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RDATE);
403bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXRULE);
404bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXDATE);
405bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_SYNC_ID);
406bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_INSTANCE_TIME);
407bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // originalAllDay, lastDate
408bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ATTENDEE_DATA);
409bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_MODIFY);
410bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_INVITE_OTHERS);
411bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_SEE_GUESTS);
412bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORGANIZER);
413c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        ALLOWED_IN_EXCEPTION.add(Events.CUSTOM_APP_PACKAGE);
414c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        ALLOWED_IN_EXCEPTION.add(Events.CUSTOM_APP_URI);
415bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // deleted, original_id, alerts
416bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
417bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
418bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Don't clone these from the base event into the exception event. */
419bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final String[] DONT_CLONE_INTO_EXCEPTION = {
420bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events._SYNC_ID,
421bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA1,
42202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA2,
42302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA3,
42402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA4,
42502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA5,
42602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA6,
427bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA7,
42802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA8,
429c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA9,
430c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA10,
431bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    };
432bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
433bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** set to 'true' to enable debug logging for recurrence exception code */
434bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final boolean DEBUG_EXCEPTION = false;
435bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
436dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
437e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
438e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
4398bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
4408bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
441420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
442420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected CalendarAlarmManager mCalendarAlarm;
443a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
444a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
445a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
446a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
447dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
448a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
449a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
450a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
451dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
452dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
453dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
454dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
455dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
456a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
457a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
458a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
459a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
473420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
477420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
479420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4908bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4918bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4928bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4938bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
494e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
495e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
496e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
497e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
498e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
499e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
500e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
5018bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
5028bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
5049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
5059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
506ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
507ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
508ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
509f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
510f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
511f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
512ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
513ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
514ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
5159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
516ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
5178bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
5188bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
519dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
520e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
521e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
522ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
523ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
5249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5254caf8d015918f619a67d321a152f150a01022717Andy McFadden        mMetaData = new MetaData(mDbHelper);
5264caf8d015918f619a67d321a152f150a01022717Andy McFadden        mInstancesHelper = new CalendarInstancesHelper(mDbHelper, mMetaData);
5274caf8d015918f619a67d321a152f150a01022717Andy McFadden
5289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
5299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
5309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
5329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
5339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
5369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
5379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
5389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
539e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
541ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
542ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
543420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // This is pulled out for testing
544420b7fb569773ae573fbe90c3a9c522d4c368863Erik        initCalendarAlarm();
545e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
546e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
5478bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
5499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
551420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected void initCalendarAlarm() {
552420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm = getOrCreateCalendarAlarmManager();
553420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.getScheduleNextAlarmWakeLock();
554e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
555e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
556e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
557420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mCalendarAlarm == null) {
558420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm = new CalendarAlarmManager(mContext);
559e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
560420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mCalendarAlarm;
561e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
562e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
563ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
564ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
565ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
566ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
567ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
568ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
569ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
570ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
571ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
572ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
573ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
574ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
575c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            try {
576c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                doUpdateTimezoneDependentFields();
577c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            } catch (IllegalStateException e) {
578c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // Added this because tests would fail if the provider is
579c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // closed by the time this is executed
580c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan
581c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // Nothing actionable here anyways.
582c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            }
583ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
584ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
585ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
58664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void verifyAccounts() {
58764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
58864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        removeStaleAccounts(AccountManager.get(getContext()).getAccounts());
58964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
59064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
59164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
5929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
595315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
5969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
5989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
6009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
6039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
606ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
6079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
611315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
612315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
613315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
614315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
615315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
616315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
617315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
618315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
6199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
6209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
6219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
622315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
623ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
624315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
625315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
626a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
627315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
628315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
629315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
630ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
631315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
632315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
633ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
634315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
635ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
636ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
637ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
638ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
639420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.rescheduleMissedAlarms();
640ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
641ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
642f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
643f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
644f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
645ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
646ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
647ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
648ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
649ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
650f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
651f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
652f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
653ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
6549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
655ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
656ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
657315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
659ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
6603443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
661ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
662315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
663ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
664ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
665ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
666ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
667ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
668ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
669ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
6703443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
6713443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
672ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
673ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
674ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
675ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
676ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6773443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
678f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
679f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
680f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
681f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
682f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
683f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
684f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
685ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6863443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
687ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
688ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
689ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
690ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
691ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
692ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
693ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
694ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
695ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
696ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
697ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
698f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
699f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
700f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
701ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
702ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
703ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
704ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
705ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
706ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
707f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
708f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
709f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
710ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
711ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
712ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
713ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
714ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
715ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
716ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
717ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
718ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
719b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
720b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
721ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
722b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
723dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
724ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
725ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
726ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
727ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
728ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
729ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
730ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
731ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
732ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
733ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
734ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
735f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
736f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
737f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
738ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
739ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
7409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
741ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
742ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
743ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
744ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
745315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
746315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
747ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
748ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
749ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
750ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
751ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
75225e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
753ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
754315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
755315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
756ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
757ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
758f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
759f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
760f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
761ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
762ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
763ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
764315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
765315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
766315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
767315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
768315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
769ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
7709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
7719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
7729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
7739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
774315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
775315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7811f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7841f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7851f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7861f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7871f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7881f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7891f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
7902ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* selection */, null,
7912ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* sort */,
792d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
793315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
7942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    instancesTimezone, isHomeTimezone());
7951f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
7961f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
7971f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
7981f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7991f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
801420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.rescheduleMissedAlarms();
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
806b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
809b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        mContentResolver.notifyChange(CalendarContract.CONTENT_URI, null, syncToNetwork);
8109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
8139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
8149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
815ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
816ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
8179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8188d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
8199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
8209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
8229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
8239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
824315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
8259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
8289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
829fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selection, selectionArgs,
8309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
831fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden            case SYNCSTATE_ID:
832fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                String selectionWithId = (SyncState._ID + "=?")
833fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                    + (selection == null ? "" : " AND (" + selection + ")");
834fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                // Prepend id to selectionArgs
835fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                selectionArgs = insertSelectionArg(selectionArgs,
836fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        String.valueOf(ContentUris.parseId(uri)));
837fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selectionWithId,
838fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        selectionArgs, sortOrder);
8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
8411ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
8438d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8448d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8459ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
8469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
8481ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
850636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
851b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
85319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
85419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
85519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
85619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
8578d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8588d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8599ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
86019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
86119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
86219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
86319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
864636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
865b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
86619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
86719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
8682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
8692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setTables(Tables.COLORS);
8702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setProjectionMap(sColorsProjectionMap);
8718d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8728d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
8742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
8759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
87643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
877b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
8788d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8798d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
88243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
883b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
884636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
885b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
8909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
8919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
8939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
8959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
903315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
9042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceQuery(qb, begin, end, projection, selection, selectionArgs,
9052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        sortOrder, match == INSTANCES_BY_DAY, false /* don't force an expansion */,
906315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
90781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
90881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
90981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
91081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
91181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
91281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
91381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
91481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
91581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
91681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
91781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
91881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
91981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
92081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
921315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
92281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
92381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
9242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceSearchQuery(qb, begin, end, query, projection, selection,
9252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        selectionArgs, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
926315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9276db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
9309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
9349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
9409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
942315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
943315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
944315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
94602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
948ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan                qb.appendWhere(SQL_WHERE_ATTENDEE_BASE);
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
95102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
953636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
954b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
957b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
96002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
962636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
963b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
966b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
968b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
971b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
973b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
977b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
979636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
980b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
9819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
983b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
9849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
986b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
987636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
988b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
9899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
990315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
991b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
992315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
993315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
10009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10028d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private void validateUriParameters(Set<String> queryParameterNames) {
10038d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        final Set<String> parameterNames = queryParameterNames;
10048d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        for (String parameterName : parameterNames) {
10058d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            if (!ALLOWED_URI_PARAMETERS.contains(parameterName)) {
10068d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                throw new IllegalArgumentException("Invalid URI parameter: " + parameterName);
10078d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            }
10088d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        }
10098d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    }
10108d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
10139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
1014ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
101539c65e5716e21e863d8de587d139dae85f99422fFred Quintana        if (projection != null && projection.length == 1
101639c65e5716e21e863d8de587d139dae85f99422fFred Quintana                && BaseColumns._COUNT.equals(projection[0])) {
101739c65e5716e21e863d8de587d139dae85f99422fFred Quintana            qb.setProjectionMap(sCountProjectionMap);
101839c65e5716e21e863d8de587d139dae85f99422fFred Quintana        }
101939c65e5716e21e863d8de587d139dae85f99422fFred Quintana
1020ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1021ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
1022ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
1023ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
1024ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
1025ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
1026ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
1027ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
10289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
10299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
10309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
10319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
1032b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            c.setNotificationUri(mContentResolver, CalendarContract.Events.CONTENT_URI);
10339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
10359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
10409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
10419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
10429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
10439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
10449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
10459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
10469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
10479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
1048d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1049315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1050315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
10519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
10529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
10542ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            long rangeEnd, String[] projection, String selection, String[] selectionArgs,
10552ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String sort, boolean searchByDay, boolean forceExpansion,
10562ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
10579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
105881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
10599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
10609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
10619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
10629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
1063315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
10649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
10659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
10669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
10679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
10689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1069315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
1070315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1071b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
10729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
10739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1074315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
1075315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1076b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
10779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10782ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10792ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = new String[] {String.valueOf(rangeEnd),
10808335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
10812ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
10822ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
10832ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
10842ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
10852ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
10862ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
10872ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10888335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
10897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
10909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
109281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
10932ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * Combine a set of arrays in the order they are passed in. All arrays must
10942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * be of the same type.
10952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
10962ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static <T> T[] combine(T[]... arrays) {
10972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (arrays.length == 0) {
10982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            throw new IllegalArgumentException("Must supply at least 1 array to combine");
10992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int totalSize = 0;
11022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
11032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            totalSize += array.length;
11042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        T[] finalArray = (T[]) (Array.newInstance(arrays[0].getClass().getComponentType(),
11072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                totalSize));
11082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int currentPos = 0;
11102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
11112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            int length = array.length;
11122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            System.arraycopy(array, 0, finalArray, currentPos, length);
11132ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            currentPos += array.length;
11142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        return finalArray;
11162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    }
11172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
1119dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1120dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1121dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1122dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1123dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1124dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1125dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1126dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1127dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1128dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1129dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
113081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1131dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1132dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
113381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
113481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
113581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
113681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
113781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
113881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1139dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1140dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1141dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1142dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1143dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1144dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1145dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1146dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1147dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1148dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1149dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1150dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1151dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1152dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
115381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
115481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
115581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
115681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
115781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
115881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
115981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
116081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
116181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
116281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
116381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
116481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
116581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
116681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
116781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
116881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
116981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
117081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
117181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
1172cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * approximation of full-text search across multiple columns.  The set
1173cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * of columns is specified by the SEARCH_COLUMNS constant.
1174cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * <p>
1175cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * Note the "WHERE" token isn't part of the returned string.  The value
1176cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * may be passed into a query as the "HAVING" clause.
117781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
117881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
117981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
118081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
118181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
118281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
118381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
118481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
118581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
118681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
118781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
118881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1189dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1190dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1191dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
119281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
119381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
119481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
119581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
119618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
119718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
119818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
119918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
120081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
120181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
120281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
120381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
120481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
120581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
120618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
120718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
120881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
120918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
121018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
121118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
121281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1213f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1214f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
121518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
121681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
121781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
121881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
121981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
122081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
122181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
122281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
12232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String selection, String[] selectionArgs, String sort, boolean searchByDay,
12242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
122518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
122681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
122781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1228dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
12292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
12302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
12312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
12322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
12332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
12342ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
12352ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
12362ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
123718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
123818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1239dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1240dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
124181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
124281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
124381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1244315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
124581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
124681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
124781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
124881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
124981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
125018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
125118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
125256292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
125356292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1254315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1255315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1256315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
125756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1258b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
125981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
126081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
126118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
126218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
126356292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
126456292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1265315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1266315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1267315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
126856292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1269b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
127081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
127181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
127218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
1273c3780839fd044b5d8109860b57a199a2da1d804fMichael Chan                Tables.INSTANCES + "." + Instances._ID /* groupBy */,
1274cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden                searchWhere /* having */, sort);
127581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
127681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
12776db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1278315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1279315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
128081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
12816db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
128243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
128343556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1284315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1285192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
128643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
128743556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1288192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
128943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1290315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1291315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1292b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
12938335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
12948335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
12958335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
12966db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
13019ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * table.  Acquires the database lock and calls
13029ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #acquireInstanceRangeLocked(long, long, boolean, boolean, String, boolean)}.
13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1307d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1308315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1309315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1311d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1312315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1313315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
13149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1316315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1317315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
13199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1331315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1332315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1333315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1335420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1336315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1340d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (DEBUG_INSTANCES) {
1341d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            Log.d(TAG + "-i", "acquireInstanceRange begin=" + begin + " end=" + end +
1342d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    " useMin=" + useMinimumExpansionWindow + " force=" + forceExpansion);
1343d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1344d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1345315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1346315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1347315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1348315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1349315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
13549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
13569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
13589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
13599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
13659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
13669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
13679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1368315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1369315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1370315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1371315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1372315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1373315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1374315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
13757be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
13767be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
13777be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
13787be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1379315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1380315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1381315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1382d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
1383d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1384d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "Wiping instances and expanding from scratch");
1385d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1386d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1388b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1389f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13906db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
13919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
13929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1393f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1394315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1395315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
13969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1397315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
13987be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
13997be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
14007be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
14017be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
14027be45683e367bd6897daf6444b03be938f8f5eaaErik
14037be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1404315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
14057be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
14067be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
14077be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
14087be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1409315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
14169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
14179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
14189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
14209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
14219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
14229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
14239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
14249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1425d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1426d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "instances are already expanded");
1427d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1428f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
14299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
14309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
14319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1438f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1445f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
14479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1450315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
14519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
14569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
14619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
14629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
14639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
14649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
14689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
14736db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
147548587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
147648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1477315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1478315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
14819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1484b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    /**
1485b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     * Determines if the event is recurrent, based on the provided values.
1486b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     */
1487b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalId,
1488b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            String originalSyncId) {
1489b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden        return (!TextUtils.isEmpty(rrule) ||
1490b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(rdate) ||
1491b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalId) ||
1492b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalSyncId));
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1495646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1496646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1497d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
1498646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1499d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * corrects the fields DTSTART, DTEND, and DURATION if necessary.
1500646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1501d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values The values to check and correct
1502d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param modValues Any updates will be stored here.  This may be the same object as
1503d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   <strong>values</strong>.
1504646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1505646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1506d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean fixAllDayTime(ContentValues values, ContentValues modValues) {
1507499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        Integer allDayObj = values.getAsInteger(Events.ALL_DAY);
1508499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        if (allDayObj == null || allDayObj == 0) {
1509d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            return false;
1510d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1511d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1512646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1513646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1514d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtstart = values.getAsLong(Events.DTSTART);
1515d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtend = values.getAsLong(Events.DTEND);
1516d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String duration = values.getAsString(Events.DURATION);
1517d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Time time = new Time();
1518d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String tempValue;
1519d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1520d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // Change dtstart so h,m,s are 0 if necessary.
1521d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.clear(Time.TIMEZONE_UTC);
1522d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.set(dtstart.longValue());
1523d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1524d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.hour = 0;
1525d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.minute = 0;
1526d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.second = 0;
1527d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            modValues.put(Events.DTSTART, time.toMillis(true));
1528d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            neededCorrection = true;
1529d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1530d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1531d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // If dtend exists for this event make sure it's h,m,s are 0.
1532d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (dtend != null) {
1533646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1534d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.set(dtend.longValue());
1535646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1536646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1537646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1538646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1539d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                dtend = time.toMillis(true);
1540d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DTEND, dtend);
1541646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1542646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1543d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1544646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1545d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (duration != null) {
1546d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            int len = duration.length();
1547d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1548d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             * in the seconds format, and if so converts it to days.
1549d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             */
1550d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (len == 0) {
1551d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = null;
1552d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else if (duration.charAt(0) == 'P' &&
1553d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    duration.charAt(len - 1) == 'S') {
1554d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int seconds = Integer.parseInt(duration.substring(1, len - 1));
1555d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1556d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = "P" + days + "D";
1557d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DURATION, duration);
1558d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                neededCorrection = true;
1559646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1560646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1561d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1562646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1563646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1564646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1565bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1566bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1567bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Determines whether the strings in the set name columns that may be overridden
1568bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * when creating a recurring event exception.
1569bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1570bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This uses a white list because it screens out unknown columns and is a bit safer to
1571bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * maintain than a black list.
1572bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1573bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private void checkAllowedInException(Set<String> keys) {
1574bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        for (String str : keys) {
1575bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!ALLOWED_IN_EXCEPTION.contains(str.intern())) {
1576bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                throw new IllegalArgumentException("Exceptions can't overwrite " + str);
1577bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1578bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1579bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1580bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1581bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
158232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * Splits a recurrent event at a specified instance.  This is useful when modifying "this
158332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * and all future events".
158432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
158532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence rule has a COUNT specified, we need to split that at the point of the
158632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * exception.  If the exception is instance N (0-based), the original COUNT is reduced
158732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * to N, and the exception's COUNT is set to (COUNT - N).
158832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
158932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence doesn't have a COUNT, we need to update or introduce an UNTIL value,
159032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * so that the original recurrence will end just before the exception instance.  (Note
159132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * that UNTIL dates are inclusive.)
159232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
159332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * This should not be used to update the first instance ("update all events" action).
1594bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
159532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @param values The original event values; must include EVENT_TIMEZONE and DTSTART.
159632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        The RRULE value may be modified (with the expectation that this will propagate
159732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        into the exception event).
1598bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param endTimeMillis The time before which the event must end (i.e. the start time of the
1599bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *        exception event instance).
160032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @return Values to apply to the original event.
1601bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1602bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static ContentValues setRecurrenceEnd(ContentValues values, long endTimeMillis) {
160332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        boolean origAllDay = values.getAsBoolean(Events.ALL_DAY);
160432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        String origRrule = values.getAsString(Events.RRULE);
1605bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
160632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        EventRecurrence origRecurrence = new EventRecurrence();
160732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        origRecurrence.parse(origRrule);
1608bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
160932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        // Get the start time of the first instance in the original recurrence.
161032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        long startTimeMillis = values.getAsLong(Events.DTSTART);
1611bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time dtstart = new Time();
1612bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.timezone = values.getAsString(Events.EVENT_TIMEZONE);
161332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        dtstart.set(startTimeMillis);
1614bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1615bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ContentValues updateValues = new ContentValues();
161632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
161732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        if (origRecurrence.count > 0) {
161832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            /*
161932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * Generate the full set of instances for this recurrence, from the first to the
162032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * one just before endTimeMillis.  The list should never be empty, because this method
162132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * should not be called for the first instance.  All we're really interested in is
162232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * the *number* of instances found.
162332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             */
162432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceSet recurSet = new RecurrenceSet(values);
162532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceProcessor recurProc = new RecurrenceProcessor();
162632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            long[] recurrences;
162732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            try {
162832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis);
162932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            } catch (DateException de) {
163032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException(de);
163132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
163232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
163332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (recurrences.length == 0) {
163432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException("can't use this method on first instance");
163532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
163632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
163732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            EventRecurrence excepRecurrence = new EventRecurrence();
16381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            excepRecurrence.parse(origRrule); // TODO: add/use a copy constructor to EventRecurrence
163932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            excepRecurrence.count -= recurrences.length;
164032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            values.put(Events.RRULE, excepRecurrence.toString());
164132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
164232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.count = recurrences.length;
164332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
164432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        } else {
164532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            Time untilTime = new Time();
164632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
164732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // The "until" time must be in UTC time in order for Google calendar
164832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // to display it properly. For all-day events, the "until" time string
164932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // must include just the date field, and not the time field. The
165032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // repeating events repeat up to and including the "until" time.
165132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.timezone = Time.TIMEZONE_UTC;
165232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
165332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // Subtract one second from the exception begin time to get the "until" time.
165432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis)
165532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (origAllDay) {
165632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.hour = untilTime.minute = untilTime.second = 0;
165732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.allDay = true;
165832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.normalize(false);
165932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
166032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // This should no longer be necessary -- DTSTART should already be in the correct
166132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // format for an all-day event.
166232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.hour = dtstart.minute = dtstart.second = 0;
166332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.allDay = true;
166432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.timezone = Time.TIMEZONE_UTC;
166532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
166632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.until = untilTime.format2445();
166732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        }
166832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
166932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        updateValues.put(Events.RRULE, origRecurrence.toString());
1670bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.DTSTART, dtstart.normalize(true));
1671bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        return updateValues;
1672bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1673bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1674bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1675bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Handles insertion of an exception to a recurring event.
1676bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1677bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * There are two modes, selected based on the presence of "rrule" in modValues:
1678bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <ol>
1679bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Create a single instance exception ("modify current event only").
1680bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Cap the original event, and create a new recurring event ("modify this and all
1681bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * future events").
1682bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * </ol>
1683bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This may be used for "modify all instances of the event" by simply selecting the
1684bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * very first instance as the exception target.  In that case, the ID of the "new"
1685bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * exception event will be the same as the originalEventId.
1686bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1687bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param originalEventId The _id of the event to be modified
1688bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param modValues Event columns to update
1689c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden     * @param callerIsSyncAdapter Set if the content provider client is the sync adapter
1690bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return the ID of the new "exception" event, or -1 on failure
1691bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1692c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden    private long handleInsertException(long originalEventId, ContentValues modValues,
1693c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            boolean callerIsSyncAdapter) {
1694bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (DEBUG_EXCEPTION) {
1695bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            Log.i(TAG, "RE: values: " + modValues.toString());
1696bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1697bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1698bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Make sure they have specified an instance via originalInstanceTime.
1699bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Long originalInstanceTime = modValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1700bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (originalInstanceTime == null) {
1701bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            throw new IllegalArgumentException("Exceptions must specify " +
1702bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Events.ORIGINAL_INSTANCE_TIME);
1703bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1704bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1705bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Check for attempts to override values that shouldn't be touched.
1706bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        checkAllowedInException(modValues.keySet());
1707bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1708c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // If this isn't the sync adapter, set the "dirty" flag in any Event we modify.
1709c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        if (!callerIsSyncAdapter) {
1710c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            modValues.put(Events.DIRTY, true);
1711c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        }
1712c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1713bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Wrap all database accesses in a transaction.
1714bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        mDb.beginTransaction();
1715bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Cursor cursor = null;
1716bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        try {
1717bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: verify that there's an instance corresponding to the specified time
1718bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (does this matter? it's weird, but not fatal?)
1719bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1720bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Grab the full set of columns for this event.
1721bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor = mDb.query(Tables.EVENTS, null /* columns */,
1722bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    SQL_WHERE_ID, new String[] { String.valueOf(originalEventId) },
1723bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    null /* groupBy */, null /* having */, null /* sortOrder */);
1724bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor.getCount() != 1) {
1725bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event ID " + originalEventId + " lookup failed (count is " +
1726bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        cursor.getCount() + ")");
1727bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1728bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1729bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //DatabaseUtils.dumpCursor(cursor);
1730bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
17312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If there's a color index check that it's valid
1732387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_index = modValues.getAsString(Events.EVENT_COLOR_KEY);
17332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_index)) {
17342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                int calIdCol = cursor.getColumnIndex(Events.CALENDAR_ID);
17352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calId = cursor.getLong(calIdCol);
17362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
17372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
17382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calId != null) {
17392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calId);
17402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
17412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
17422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
17432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
17442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
17452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_index, Colors.TYPE_EVENT);
17462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
17472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1748bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1749bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * Verify that the original event is in fact a recurring event by checking for the
1750bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * presence of an RRULE.  If it's there, we assume that the event is otherwise
1751bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * properly constructed (e.g. no DTEND).
1752bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1753bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor.moveToFirst();
1754bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int rruleCol = cursor.getColumnIndex(Events.RRULE);
1755bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (TextUtils.isEmpty(cursor.getString(rruleCol))) {
1756bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event has no rrule");
1757bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1758bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1759bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (DEBUG_EXCEPTION) {
1760bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.d(TAG, "RE: old RRULE is " + cursor.getString(rruleCol));
1761bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1762bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1763bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Verify that the original event is not itself a (single-instance) exception.
1764bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int originalIdCol = cursor.getColumnIndex(Events.ORIGINAL_ID);
1765bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!TextUtils.isEmpty(cursor.getString(originalIdCol))) {
1766bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event is an exception");
1767bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1768bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1769bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1770bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createSingleException = TextUtils.isEmpty(modValues.getAsString(Events.RRULE));
1771bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1772bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: check for the presence of an existing exception on this event+instance?
1773bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       The caller should be modifying that, not creating another exception.
1774bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (Alternatively, we could do that for them.)
1775bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1776bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Create a new ContentValues for the new event.  Start with the original event,
1777bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // and drop in the new caller-supplied values.  This will set originalInstanceTime.
1778bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            ContentValues values = new ContentValues();
1779bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
1780f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor.close();
1781f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor = null;
1782bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1783b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            // TODO: if we're changing this to an all-day event, we should ensure that
1784b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       hours/mins/secs on DTSTART are zeroed out (before computing DTEND).
1785b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       See fixAllDayTime().
1786b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1787bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createNewEvent = true;
1788bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createSingleException) {
1789bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1790bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Save a copy of a few fields that will migrate to new places.
1791bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1792bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _id = values.getAsString(Events._ID);
1793bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _sync_id = values.getAsString(Events._SYNC_ID);
1794bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1795bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1796bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Wipe out some fields that we don't want to clone into the exception event.
1798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                for (String str : DONT_CLONE_INTO_EXCEPTION) {
1800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(str);
1801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1803bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1804bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Merge the new values on top of the existing values.  Note this sets
1805bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * originalInstanceTime.
1806bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1807bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.putAll(modValues);
1808bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1809bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1810bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Copy some fields to their "original" counterparts:
1811bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _id --> original_id
1812bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _sync_id --> original_sync_id
1813bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   allDay --> originalAllDay
1814bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1815bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this event hasn't been sync'ed with the server yet, the _sync_id field will
1816bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * be null.  We will need to fill original_sync_id in later.  (May not be able to
1817bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * do it right when our own _sync_id field gets populated, because the order of
1818bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * events from the server may not be what we want -- could update the exception
1819bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * before updating the original event.)
1820bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1821bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * _id is removed later (right before we write the event).
1822bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1823bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ID, _id);
1824bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_SYNC_ID, _sync_id);
1825bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ALL_DAY, allDay);
1826bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1827bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // Mark the exception event status as "tentative", unless the caller has some
1828bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // other value in mind (like STATUS_CANCELED).
1829bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (!values.containsKey(Events.STATUS)) {
1830bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
1831bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1832bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1833bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // We're converting from recurring to non-recurring.  Clear out RRULE and replace
1834bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // DURATION with DTEND.
1835c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                values.remove(Events.RRULE);
1836bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1837bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Duration duration = new Duration();
1838bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String durationStr = values.getAsString(Events.DURATION);
1839bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                try {
1840bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    duration.parse(durationStr);
1841bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } catch (Exception ex) {
1842bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // NullPointerException if the original event had no duration.
1843bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // DateException if the duration was malformed.
1844bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Bad duration in recurring event: " + durationStr, ex);
1845bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1846bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1847bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1848c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1849c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * We want to compute DTEND as an offset from the start time of the instance.
1850c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If the caller specified a new value for DTSTART, we want to use that; if not,
1851c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the DTSTART in "values" will be the start time of the first instance in the
1852c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * recurrence, so we want to replace it with ORIGINAL_INSTANCE_TIME.
1853c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1854c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                long start;
1855c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.DTSTART)) {
1856c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.DTSTART);
1857c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                } else {
1858c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1859c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    values.put(Events.DTSTART, start);
1860c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1861bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.DTEND, start + duration.getMillis());
1862bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1863c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    Log.d(TAG, "RE: ORIG_INST_TIME=" + start +
1864c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            ", duration=" + duration.getMillis() +
1865bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            ", generated DTEND=" + values.getAsLong(Events.DTEND));
1866bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
186785c09a31bcc3a18e173428bf7b628cec2834bebcAndy McFadden                values.remove(Events.DURATION);
1868bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1869bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1870bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * We're going to "split" the recurring event, making the old one stop before
1871bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * this instance, and creating a new recurring event that starts here.
1872bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1873bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * No need to fill out the "original" fields -- the new event is not tied to
1874bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * the previous event in any way.
1875bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1876bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this is the first event in the series, we can just update the existing
1877bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * event with the values.
1878bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1879bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean canceling = (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
1880bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1881bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (originalInstanceTime.equals(values.getAsLong(Events.DTSTART))) {
1882bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1883bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Update fields in the existing event.  Rather than use the merged data
1884bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * from the cursor, we just do the update with the new value set after
1885bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * removing the ORIGINAL_INSTANCE_TIME entry.
1886bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1887bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (canceling) {
1888bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        // TODO: should we just call deleteEventInternal?
1889bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "Note: canceling entire event via exception call");
1890bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1891bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1892bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: updating full event");
1893bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1894ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    if (!validateRecurrenceRule(modValues)) {
1895ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        throw new IllegalArgumentException("Invalid recurrence rule: " +
1896ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                                values.getAsString(Events.RRULE));
1897ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    }
1898bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    modValues.remove(Events.ORIGINAL_INSTANCE_TIME);
1899bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
1900bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1901bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    createNewEvent = false; // skip event creation and related-table cloning
1902bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } else {
1903bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1904bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: splitting event");
1905bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1906bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1907bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
190832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Cap the original event so it ends just before the target instance.  In
190932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * some cases (nonzero COUNT) this will also update the RRULE in "values",
191032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * so that the exception we're creating terminates appropriately.  If a
191132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * new RRULE was specified by the caller, the new rule will overwrite our
191232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * changes when we merge the new values in below (which is the desired
191332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * behavior).
1914bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1915bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    ContentValues splitValues = setRecurrenceEnd(values, originalInstanceTime);
1916bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, splitValues, SQL_WHERE_ID,
1917bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1918bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1919bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
192032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Prepare the new event.  We remove originalInstanceTime, because we're now
1921bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * creating a new event rather than an exception.
1922bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     *
1923bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * We're always cloning a non-exception event (we tested to make sure the
1924bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * event doesn't specify original_id, and we don't allow original_id in the
1925bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * modValues), so we shouldn't end up creating a new event that looks like
1926bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * an exception.
1927bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1928bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.putAll(modValues);
1929bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(Events.ORIGINAL_INSTANCE_TIME);
1930bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1931c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            }
1932bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1933bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            long newEventId;
1934bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createNewEvent) {
1935bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.remove(Events._ID);      // don't try to set this explicitly
1936be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
1937be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(values, null);
1938be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
1939be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(values);
1940be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
1941bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1942bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = mDb.insert(Tables.EVENTS, null, values);
1943bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (newEventId < 0) {
1944bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Unable to add exception to recurring event");
1945bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Values: " + values);
1946bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1947bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1948bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1949bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.d(TAG, "RE: new ID is " + newEventId);
1950bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1951bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1952b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                // TODO: do we need to do something like this?
1953b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                //updateEventRawTimesLocked(id, updatedValues);
1954b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1955b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1956b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Force re-computation of the Instances associated with the recurrence event.
1957b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1958b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, newEventId, true, mDb);
1959b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1960bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1961bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Some of the other tables (Attendees, Reminders, ExtendedProperties) reference
1962c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the Event ID.  We need to copy the entries from the old event, filling in the
1963c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * new event ID, so that somebody doing a SELECT on those tables will find
1964c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * matching entries.
1965bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1966bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                CalendarDatabaseHelper.copyEventRelatedTables(mDb, newEventId, originalEventId);
1967c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1968c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1969c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If we modified Event.selfAttendeeStatus, we need to keep the corresponding
1970c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * entry in the Attendees table in sync.
1971c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1972c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
1973c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    /*
1974c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * Each Attendee is identified by email address.  To find the entry that
1975c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * corresponds to "self", we want to compare that address to the owner of
1976c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * the Calendar.  We're expecting to find one matching entry in Attendees.
1977c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     */
1978c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    long calendarId = values.getAsLong(Events.CALENDAR_ID);
1979f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    String accountName = getOwner(calendarId);
1980f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden
1981f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    if (accountName != null) {
1982c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        ContentValues attValues = new ContentValues();
1983c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        attValues.put(Attendees.ATTENDEE_STATUS,
1984c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                modValues.getAsString(Events.SELF_ATTENDEE_STATUS));
1985c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1986c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (DEBUG_EXCEPTION) {
1987c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.d(TAG, "Updating attendee status for event=" + newEventId +
1988c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    " name=" + accountName + " to " +
1989c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    attValues.getAsString(Attendees.ATTENDEE_STATUS));
1990c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1991c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        int count = mDb.update(Tables.ATTENDEES, attValues,
1992c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
1993c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                new String[] { String.valueOf(newEventId), accountName });
1994b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                        if (count != 1 && count != 2) {
1995b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // We're only expecting one matching entry.  We might briefly see
1996b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // two during a server sync.
19977148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                            Log.e(TAG, "Attendee status update on event=" + newEventId
19987148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                    + " touched " + count + " rows. Expected one or two rows.");
1999b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            if (false) {
2000b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                // This dumps PII in the log, don't ship with it enabled.
2001b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                Cursor debugCursor = mDb.query(Tables.ATTENDEES, null,
2002b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        Attendees.EVENT_ID + "=? AND " +
2003b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                            Attendees.ATTENDEE_EMAIL + "=?",
2004b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        new String[] { String.valueOf(newEventId), accountName },
2005b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        null, null, null);
2006b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                DatabaseUtils.dumpCursor(debugCursor);
20070332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                if (debugCursor != null) {
20080332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                    debugCursor.close();
20090332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                }
2010b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            }
2011b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            throw new RuntimeException("Status update WTF");
2012c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
2013c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    }
2014c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
2015bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
2016b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
2017b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Update any Instances changed by the update to this Event.
2018b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
2019b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, originalEventId, false, mDb);
2020bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = originalEventId;
2021bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
2022bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
2023bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.setTransactionSuccessful();
2024bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            return newEventId;
2025bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        } finally {
2026bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor != null) {
2027bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                cursor.close();
2028bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
2029bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.endTransaction();
2030bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
2031bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
2032bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
2033222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    /**
2034222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Fills in the originalId column for previously-created exceptions to this event.  If
2035222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this event is not recurring or does not have a _sync_id, this does nothing.
2036222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2037222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * The server might send exceptions before the event they refer to.  When
2038222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this happens, the originalId field will not have been set in the
2039222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * exception events (it's the recurrence events' _id field, so it can't be
2040222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * known until the recurrence event is created).  When we add a recurrence
2041222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * event with a non-empty _sync_id field, we write that event's _id to the
2042222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * originalId field of any events whose originalSyncId matches _sync_id.
2043222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2044222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Note _sync_id is only expected to be unique within a particular calendar.
2045222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     *
2046222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param id The ID of the Event
2047222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param values Values for the Event being inserted
2048222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     */
2049222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    private void backfillExceptionOriginalIds(long id, ContentValues values) {
2050222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String syncId = values.getAsString(Events._SYNC_ID);
2051222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2052222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rdate = values.getAsString(Events.RDATE);
2053222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String calendarId = values.getAsString(Events.CALENDAR_ID);
2054222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2055222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        if (TextUtils.isEmpty(syncId) || TextUtils.isEmpty(calendarId) ||
2056222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                (TextUtils.isEmpty(rrule) && TextUtils.isEmpty(rdate))) {
2057222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            // Not a recurring event, or doesn't have a server-provided sync ID.
2058222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            return;
2059222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        }
2060222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2061222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        ContentValues originalValues = new ContentValues();
2062222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        originalValues.put(Events.ORIGINAL_ID, id);
2063222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        mDb.update(Tables.EVENTS, originalValues,
2064222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                Events.ORIGINAL_SYNC_ID + "=? AND " + Events.CALENDAR_ID + "=?",
2065222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                new String[] { syncId, calendarId });
2066222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    }
2067222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
20689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2069b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
2070ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
20719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
20729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20738d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
20740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
20750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
20760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                null /* selection */, null /* selection args */);
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
2081bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case SYNCSTATE:
20829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
20839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
20857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2086c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
20877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
20898253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    if (values.containsKey(Events.ORIGINAL_SYNC_ID)
20908253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                            && values.containsKey(Events.ORIGINAL_INSTANCE_TIME)
20918253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                            && Events.STATUS_CANCELED == values.getAsInteger(Events.STATUS)) {
20928253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        // event is a canceled instance of a recurring event, it doesn't these
20938253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        // values but lets fake some to satisfy curious consumers.
20948253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        final long origStart = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
20958253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.DTSTART, origStart);
20968253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.DTEND, origStart);
20978253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC);
20988253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    } else {
20998253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        throw new RuntimeException("DTSTART field missing from event");
21008253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    }
21019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
2103e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
2104be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
2105be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(updatedValues, null);
2106be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
2107be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(updatedValues);
2108be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
2109e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
2110e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
21119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
21129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
21139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
21149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calendar_id = updatedValues.getAsLong(Events.CALENDAR_ID);
21162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calendar_id == null) {
21172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // validateEventData checks this for non-sync adapter
21182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // inserts
21192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("New events must specify a calendar id");
21202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color is valid if it is being set
2122387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = updatedValues.getAsString(Events.EVENT_COLOR_KEY);
21232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
21242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calendar_id);
21252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = null;
21262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = null;
21272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
21282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
21292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
21302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
21312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, color_id,
21322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_EVENT);
21332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    updatedValues.put(Events.EVENT_COLOR, color);
21342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
21362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!updatedValues.containsKey(Events.ORGANIZER)) {
21372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    owner = getOwner(calendar_id);
21389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
21399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
21409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
21449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
21459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
214634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
214734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
214834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    long originalId = getOriginalId(updatedValues
2149ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                            .getAsString(Events.ORIGINAL_SYNC_ID),
2150ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                            updatedValues.getAsString(Events.CALENDAR_ID));
215134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (originalId != -1) {
215234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_ID, originalId);
215334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
215434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
215534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && updatedValues.containsKey(Events.ORIGINAL_ID)) {
215634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    String originalSyncId = getOriginalSyncId(updatedValues
215734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsLong(Events.ORIGINAL_ID));
215834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (!TextUtils.isEmpty(originalSyncId)) {
215934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
216034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
216134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                }
2162d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (fixAllDayTime(updatedValues, updatedValues)) {
2163f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2164f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
2165f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
2166f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
2167646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
21681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                updatedValues.remove(Events.HAS_ALARM);     // should not be set by caller
2169c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
21719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
2173f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                    mInstancesHelper.updateInstancesLocked(updatedValues, id,
2174f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                            true /* new event */, mDb);
21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
21779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
21789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
21799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
21809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
21812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            owner = getOwner(calendar_id);
21829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
21839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
21849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2185b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
2186222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                    backfillExceptionOriginalIds(id, values);
2187222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2188dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
21899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2191bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID:
2192bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long originalEventId = ContentUris.parseId(uri);
2193c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                id = handleInsertException(originalEventId, values, callerIsSyncAdapter);
2194bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                break;
21959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
219682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden                // TODO: verify that all required fields are present
21979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
21989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
2199c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
22009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
2201c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                            Calendars.ACCOUNT_TYPE);
22029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
2203fa332ecedc0c340109811552407142f6e4f600b2RoboErik                    String eventsUrl = values.getAsString(Calendars.CAL_SYNC1);
22041b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
22059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2206387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String cal_color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
22072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(cal_color_id)) {
22082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
22092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
22102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, cal_color_id,
22112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_CALENDAR);
22122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    values.put(Calendars.CALENDAR_COLOR, color);
22132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2215dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
22169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
22182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // verifyTransactionAllowed requires this be from a sync
22192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter, all of the required fields are marked NOT NULL in
22202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // the db. TODO Do we need explicit checks here or should we
22212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // just let sqlite throw if something isn't specified?
22222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = uri.getQueryParameter(Colors.ACCOUNT_NAME);
22232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = uri.getQueryParameter(Colors.ACCOUNT_TYPE);
2224387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String colorIndex = values.getAsString(Colors.COLOR_KEY);
22252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
22262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Account name and type must be non"
22272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            + " empty parameters for " + uri);
22282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(colorIndex)) {
22302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("COLOR_INDEX must be non empty for " + uri);
22312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!values.containsKey(Colors.COLOR_TYPE) || !values.containsKey(Colors.COLOR)) {
22332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException(
22342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            "New colors must contain COLOR_TYPE and COLOR");
22352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Make sure the account we're inserting for is the same one the
22372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter is claiming to be. TODO should we throw if they
22382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // aren't the same?
22392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_NAME, accountName);
22402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_TYPE, accountType);
22412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
22422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color doesn't already exist
22432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = null;
22442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
22454755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    final long colorType = values.getAsLong(Colors.COLOR_TYPE);
22464755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
22472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.getCount() != 0) {
22484755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        throw new IllegalArgumentException("color type " + colorType
22494755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                                + " and index " + colorIndex
22507148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                + " already exists for account and type provided");
22512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
22522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
22532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null)
22542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
22552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                id = mDbHelper.colorsInsert(values);
22572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
22599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
22629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
22649ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Attendees.EVENT_ID);
22659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
22669ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
22677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22689ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.attendeesInsert(values);
22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
22741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            {
22751c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
22761c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventIdObj == null) {
22779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
22781c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            + "contain a numeric event_id");
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
22811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventIdObj);
22821c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    setEventDirty(eventIdObj);
22837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
22849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.remindersInsert(values);
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // We know this event has at least one reminder, so make sure "hasAlarm" is 1.
22871c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                setHasAlarm(eventIdObj, 1);
22881c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2293420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
22949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
22989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
23022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
23032fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
2306b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                if (!values.containsKey(CalendarContract.ExtendedProperties.EVENT_ID)) {
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2311b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    final Long eventId = values
2312b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            .getAsLong(CalendarContract.ExtendedProperties.EVENT_ID);
23139ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
23149ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
23157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
23169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.extendedPropertiesInsert(values);
23179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
23183b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            case EMMA:
23193b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                // Special target used during code-coverage evaluation.
23203b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                handleEmmaRequest(values);
23213b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                break;
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
23286db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2329315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
23307e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2342e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
23433b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * Handles special commands related to EMMA code-coverage testing.
23443b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     *
23453b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * @param values Parameters from the caller.
23463b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     */
23473b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static void handleEmmaRequest(ContentValues values) {
23483b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        /*
23493b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * This is not part of the public API, so we can't share constants with the CTS
23503b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * test code.
23513b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         *
23523b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * Bad requests, or attempting to request EMMA coverage data when the coverage libs
23533b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * aren't linked in, will cause an exception.
23543b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         */
23553b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        String cmd = values.getAsString("cmd");
23563b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        if (cmd.equals("start")) {
23573b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // We'd like to reset the coverage data, but according to FAQ item 3.14 at
23583b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // http://emma.sourceforge.net/faq.html, this isn't possible in 2.0.
23593b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            Log.d(TAG, "Emma coverage testing started");
23603b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        } else if (cmd.equals("stop")) {
23613b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // Call com.vladium.emma.rt.RT.dumpCoverageData() to cause a data dump.  We
23623b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // may not have been built with EMMA, so we need to do this through reflection.
23633b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            String filename = values.getAsString("outputFileName");
23643b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23653b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            File coverageFile = new File(filename);
23663b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            try {
23673b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
23683b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
23693b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        coverageFile.getClass(), boolean.class, boolean.class);
23703b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23713b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                dumpCoverageMethod.invoke(null, coverageFile, false /*merge*/,
23723b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        false /*stopDataCollection*/);
23733b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Log.d(TAG, "Emma coverage data written to " + filename);
23743b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            } catch (Exception e) {
23753b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                throw new RuntimeException("Emma coverage dump failed", e);
23763b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            }
23773b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        }
23783b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    }
23793b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
23803b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    /**
23815ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * Validates the recurrence rule, if any.  We allow single- and multi-rule RRULEs.
2382ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * <p>
23835ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * TODO: Validate RDATE, EXRULE, EXDATE (possibly passing in an indication of whether we
23845ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * believe we have the full set, so we can reject EXRULE when not accompanied by RRULE).
2385ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     *
2386ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * @return A boolean indicating successful validation.
2387ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     */
2388ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    private boolean validateRecurrenceRule(ContentValues values) {
2389ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2390ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2391ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        if (!TextUtils.isEmpty(rrule)) {
23925ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            String[] ruleList = rrule.split("\n");
23935ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            for (String recur : ruleList) {
23945ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                EventRecurrence er = new EventRecurrence();
23955ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                try {
23965ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    er.parse(recur);
23975ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                } catch (EventRecurrence.InvalidFormatException ife) {
23985ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    Log.w(TAG, "Invalid recurrence rule: " + recur);
2399bfea6da707f8d352432096371e7da76c230d9059Michael Chan                    dumpEventNoPII(values);
24005ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    return false;
24015ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                }
2402ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2403ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        }
2404ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2405ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        return true;
2406ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    }
2407ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2408bfea6da707f8d352432096371e7da76c230d9059Michael Chan    private void dumpEventNoPII(ContentValues values) {
2409bfea6da707f8d352432096371e7da76c230d9059Michael Chan        if (values == null) {
2410bfea6da707f8d352432096371e7da76c230d9059Michael Chan            return;
2411bfea6da707f8d352432096371e7da76c230d9059Michael Chan        }
2412bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2413bfea6da707f8d352432096371e7da76c230d9059Michael Chan        StringBuilder bob = new StringBuilder();
2414bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("dtStart:       ").append(values.getAsLong(Events.DTSTART));
2415bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndtEnd:         ").append(values.getAsLong(Events.DTEND));
2416bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nall_day:       ").append(values.getAsInteger(Events.ALL_DAY));
2417bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ntz:            ").append(values.getAsString(Events.EVENT_TIMEZONE));
2418bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndur:           ").append(values.getAsString(Events.DURATION));
2419bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrrule:         ").append(values.getAsString(Events.RRULE));
2420bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrdate:         ").append(values.getAsString(Events.RDATE));
2421bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nlast_date:     ").append(values.getAsLong(Events.LAST_DATE));
2422bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2423bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nid:            ").append(values.getAsLong(Events._ID));
2424bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nsync_id:       ").append(values.getAsString(Events._SYNC_ID));
2425bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_id:        ").append(values.getAsLong(Events.ORIGINAL_ID));
2426bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_sync_id:   ").append(values.getAsString(Events.ORIGINAL_SYNC_ID));
2427bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_inst_time: ").append(values.getAsLong(Events.ORIGINAL_INSTANCE_TIME));
2428bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_all_day:   ").append(values.getAsInteger(Events.ORIGINAL_ALL_DAY));
2429bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2430bfea6da707f8d352432096371e7da76c230d9059Michael Chan        Log.i(TAG, bob.toString());
2431bfea6da707f8d352432096371e7da76c230d9059Michael Chan    }
2432bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2433ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    /**
243462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Do some scrubbing on event data before inserting or updating. In particular make
243562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * dtend, duration, etc make sense for the type of event (regular, recurrence, exception).
243662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Remove any unexpected fields.
2437e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
243862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param values the ContentValues to insert.
243962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param modValues if non-null, explicit null entries will be added here whenever something
244062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     *   is removed from <strong>values</strong>.
2441e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
244262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden    private void scrubEventData(ContentValues values, ContentValues modValues) {
2443e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2444e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2445e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2446e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2447c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_SYNC_ID));
2448e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2449e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2450e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2451e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2452e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2453e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2454ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            // rrule is a valid recurrence rule
2455e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2456e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2457e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2458ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            if (!validateRecurrenceRule(values)) {
2459ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2460ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        values.getAsString(Events.RRULE));
2461ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2462e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
246362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DTEND, ORIGINAL_SYNC_ID, ORIGINAL_INSTANCE_TIME");
2464e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
246562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence: " + values);
2466e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2467e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2468c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                values.remove(Events.ORIGINAL_SYNC_ID);
2469e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
247062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
247162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DTEND);
247262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_SYNC_ID);
247362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_INSTANCE_TIME);
247462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2475e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2476e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2477e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2478e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2479e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2480e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2481e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2482e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2483e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2484e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2485e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
248662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2487e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
248862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence exception: " + values);
2489e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2490e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
249162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
249262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
249362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2494e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2495e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2496e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2497e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2498e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2499e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2500e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2501e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2502e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2503e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2504e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
250562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2506e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
250762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for event: " + values);
2508e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2509e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
251062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
251162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
251262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2513e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2514e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2515e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2516e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
2517d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2518d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Validates event data.  Pass in the full set of values for the event (i.e. not just
2519d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * a part that's being updated).
2520d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2521d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values Event data.
2522d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws IllegalArgumentException if bad data is found.
2523d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
2524d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private void validateEventData(ContentValues values) {
252582b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.CALENDAR_ID))) {
252682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include a calendar_id");
252782b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
252882b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.EVENT_TIMEZONE))) {
252982b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include an eventTimezone");
253082b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
253182b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden
2532d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtstart = values.getAsLong(Events.DTSTART) != null;
2533d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2534d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2535d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2536d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2537d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasRrule || hasRdate) {
2538d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!validateRecurrenceRule(values)) {
2539d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2540d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        values.getAsString(Events.RRULE));
2541d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
2542d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2543d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
2544d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDtstart) {
2545bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2546d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTSTART cannot be empty.");
2547d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2548d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDuration && !hasDtend) {
2549bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2550d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
2551d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    "an event.");
2552d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2553d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasDuration && hasDtend) {
2554bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2555d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("Cannot have both DTEND and DURATION in an event");
2556d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2557d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
2558d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
25599ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private void setEventDirty(long eventId) {
25609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Object[] {eventId});
25617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
25627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
2563ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson    private long getOriginalId(String originalSyncId, String calendarId) {
2564ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson        if (TextUtils.isEmpty(originalSyncId) || TextUtils.isEmpty(calendarId)) {
256534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return -1;
256634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
256734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
256834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        long originalId = -1;
256934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
257034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
257134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
2572ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                    Events._SYNC_ID + "=?"  + " AND " + Events.CALENDAR_ID + "=?",
2573ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                    new String[] {originalSyncId, calendarId}, null);
257434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
257534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalId = c.getLong(0);
257634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
257734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
257834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
257934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
258034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
258134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
258234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalId;
258334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
258434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
258534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private String getOriginalSyncId(long originalId) {
258634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (originalId == -1) {
258734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return null;
258834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
258934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
259034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        String originalSyncId = null;
259134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
259234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
259334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
259434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
259534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
259634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalSyncId = c.getString(0);
259734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
259834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
259934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
260034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
260134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
260234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
260334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalSyncId;
260434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
260534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
26064755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private Cursor getColorByTypeIndex(String accountName, String accountType, long colorType,
26074755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            String colorIndex) {
26084755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        return mDb.query(Tables.COLORS, COLORS_PROJECTION, COLOR_FULL_SELECTION, new String[] {
26094755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                accountName, accountType, Long.toString(colorType), colorIndex
26104755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        }, null, null, null);
26112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
26122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
26139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2614f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * Gets a calendar's "owner account", i.e. the e-mail address of the owner of the calendar.
2615f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     *
2616f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * @param calId The calendar ID.
26179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
26189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
26199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2620f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2621f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2622f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2623f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2624f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2625f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
26269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
26279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
26289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
26299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
26309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
26319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
26329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
26339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
26349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
26359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2636f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2637f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2638f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
26399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
26409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
26429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
26439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
26449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
26459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private Account getAccount(long calId) {
26512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Account account = null;
26522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor cursor = null;
26532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
26542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
26552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ACCOUNT_PROJECTION, null /* selection */, null /* selectionArgs */,
26562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    null /* sort */);
26572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor == null || !cursor.moveToFirst()) {
26582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (Log.isLoggable(TAG, Log.DEBUG)) {
26592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
26602f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
26612f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                return null;
26622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
26632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            account = new Account(cursor.getString(ACCOUNT_NAME_INDEX),
26642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.getString(ACCOUNT_TYPE_INDEX));
26652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
26662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor != null) {
26672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                cursor.close();
26682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
26692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
26702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return account;
26712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
26722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
26789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
26799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
26809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
26819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
26829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
26839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
26899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
26909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
26919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
26929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
26959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
26969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
27019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
27029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
270324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param attendeeValues the column values for one row in the Attendees table.
27049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
27059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
27069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
270724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Long eventIdObj = attendeeValues.getAsLong(Attendees.EVENT_ID);
270824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (eventIdObj == null) {
270924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            Log.w(TAG, "Attendee update values don't include an event_id");
271024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            return;
271124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
271224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        long eventId = eventIdObj;
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
27179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
27189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
27199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2725f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2726f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2727f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
27319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
27329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
27339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
27349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
27389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
27399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
27409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
27419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
27429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
27439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
27449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
27459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
27469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2747f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2748f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2749f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
27509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
27519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
27539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
27579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
27609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
27619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
27659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
27669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
27679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
27729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
27739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
277724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // Select a default value for "status" based on the relationship.
27789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
277924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer relationObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
278024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (relationObj != null) {
278124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            int rel = relationObj;
27829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
27849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
278724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // If the status is specified, use that.
278824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer statusObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
278924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (statusObj != null) {
279024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            status = statusObj;
27919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
27949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2795b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2796b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
27979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2799d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
28001c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Set the "hasAlarm" column in the database.
28011c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
28021c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param eventId The _id of the Event to update.
28031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param val The value to set it to (0 or 1).
28041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
28051c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private void setHasAlarm(long eventId, int val) {
28061c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues values = new ContentValues();
28071c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        values.put(Events.HAS_ALARM, val);
28081c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int count = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
28091c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                new String[] { String.valueOf(eventId) });
28101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (count != 1) {
28111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Log.w(TAG, "setHasAlarm on event " + eventId + " updated " + count +
28121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    " rows (expected 1)");
28131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
28141c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
28151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
28161c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
2817d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Calculates the "last date" of the event.  For a regular event this is the start time
2818d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * plus the duration.  For a recurring event this is the start date of the last event in
2819d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence, plus the duration.  The event recurs forever, this returns -1.  If
2820d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence rule can't be parsed, this returns -1.
2821d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2822d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values
2823d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the date, in milliseconds, since the start of the epoch (UTC), or -1 if an
2824d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   exceptional condition exists.
2825d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws DateException
2826d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
28369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
28419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
28439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
28449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
28489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
28499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
28509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
28519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
28529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
28539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
28549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
28559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
28569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
28579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
28589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
28599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2861f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2862f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2863f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2864f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2865f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2866f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2867b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            values.get(CalendarContract.Events.RRULE), e);
2868f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2869d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: this should throw an exception or return a distinct error code
2870f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2871f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
28729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2873f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
28749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
28759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
28769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
28789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
28809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
28819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
28829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
28849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
28869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
28889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
28899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
2890d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // repeats forever
28919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
28929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
28949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
28959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
28969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
28999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
29009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
29029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2904e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2905e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
290682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     * @param values the ContentValues (in/out); must include DTSTART and, if the event is
290782b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   recurring, the columns necessary to process a recurrence rule (RRULE, DURATION,
290882b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   EVENT_TIMEZONE, etc).
2909e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2910e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2911e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
29139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
29199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
29209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2921f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2922f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2923f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2928d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2929d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Creates or updates an entry in the EventsRawTimes table.
2930d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2931d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param eventId The ID of the event that was just created or is being updated.
2932d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values For a new event, the full set of event values; for an updated event,
2933d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   the set of values that are being changed.
2934d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
29359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
29379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2938b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        rawValues.put(CalendarContract.EventsRawTimes.EVENT_ID, eventId);
29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
29419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
29439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
29449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
29459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
29469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
29499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
29509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
29519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
29549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
29559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
29569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
29579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
2958b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTSTART_2445, time.format2445());
29599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
29629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
29639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
2964b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTEND_2445, time.format2445());
29659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
29719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
29729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
29739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
29749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
29759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
2977b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445,
2978b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
29799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
29829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
29839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
29849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
2985b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.LAST_DATE_2445, time.format2445());
29869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
29899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2992b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2993b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2994ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
29959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
29969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29978d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
29989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
29990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
30000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
30010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
30029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
30039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
30049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
30059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
30072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
30089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
30099323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3010dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3011dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3012dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
3013dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
30149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
30168d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                return deleteMatchingColors(appendAccountToSelection(uri, selection,
30178d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE),
30182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
30192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
30201ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
30219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30227e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
30238d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(
30248d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        uri, selection, Events.ACCOUNT_NAME, Events.ACCOUNT_TYPE);
30257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
30261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
3027ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak                Cursor cursor = mDb.query(Views.EVENTS, ID_ONLY_PROJECTION,
30281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
30297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
30309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
30311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
30321ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
303310b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
30349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
3035420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3036dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
30379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
30389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
30399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
30409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
30419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
30429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30431ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
30441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
30451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
304610b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
30471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
3048bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID2:
3049bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            {
3050bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // This will throw NumberFormatException on missing or malformed input.
3051bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                List<String> segments = uri.getPathSegments();
3052bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long eventId = Long.parseLong(segments.get(1));
3053bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long excepId = Long.parseLong(segments.get(2));
3054bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // TODO: verify that this is an exception instance (has an ORIGINAL_ID field
3055bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //       that matches the supplied eventId)
3056bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return deleteEventInternal(excepId, callerIsSyncAdapter, false /* isBatch */);
3057bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
30589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3061b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
30627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
30631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, selection,
30641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
30657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
30689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
30707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3071b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
3072b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
30737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
30741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, null /* selection */,
30752fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
30767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
30799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30801c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, false, selection, selectionArgs, callerIsSyncAdapter);
30819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
30839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
30841c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, true, null /*selection*/, null /*selectionArgs*/,
30851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        callerIsSyncAdapter);
30862fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
30872fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
30882fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
30892fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
3090b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
30912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
30921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri, selection,
3093b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
30942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
30952fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
30962fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
30972fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
30982fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
30992fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
3100b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
3101636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
31022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
31031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri,
31041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            null /* selection */, null /* selectionArgs */);
31057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
31089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3110b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
31117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
31121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.CALENDAR_ALERTS, uri, selection,
31131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
31147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
31179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31182fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
31192fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
31209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3121b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
3122b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
31239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
31252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                StringBuilder selectionSb = new StringBuilder(Calendars._ID + "=");
31269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
31279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
31289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
31299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
31309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
31319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
313324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                // $FALL-THROUGH$ - fall through to CALENDARS for the actual delete
31349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
31358d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
31368d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
313774ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                return deleteMatchingCalendars(selection, selectionArgs);
31389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
31399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
31406db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
3141315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
31429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
31439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
31449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
31459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
31469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
31479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
314810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
31491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
3150192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
31511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
31521ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
3153b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
3154b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
3155636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
31561ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
31571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
31581ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
31591ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
31601ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
316148f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
31621ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
31631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
31641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
31651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
31661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
31671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
3168b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origId = cursor.getString(EVENTS_ORIGINAL_ID_INDEX);
3169b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origSyncId = cursor.getString(EVENTS_ORIGINAL_SYNC_ID_INDEX);
3170b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                if (isRecurrenceEvent(rrule, rdate, origId, origSyncId)) {
31711ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
31721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
31734d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                boolean isRecurrence = !TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate);
31741ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
317548f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
317648f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
3177bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //
3178bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // The EVENTS_CLEANUP_TRIGGER_SQL trigger will remove all associated data
3179bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // (Attendees, Instances, Reminders, etc).
318048f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
3181b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
31824d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
31834d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // If this is a recurrence, and the event was never synced with the server,
31844d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we want to delete any exceptions as well.  (If it has been to the server,
31854d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we'll let the sync adapter delete the events explicitly.)  We assume that,
31864d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // if the recurrence hasn't been synced, the exceptions haven't either.
31874d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    if (isRecurrence && emptySyncId) {
31884d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                        mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID, selectionArgs);
31894d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    }
31901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
3191bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // Event is on the server, so we "soft delete", i.e. mark as deleted so that
3192bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the sync adapter has a chance to tell the server about the deletion.  After
3193bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the server sees the change, the sync adapter will do the "hard delete"
3194bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // (above).
31951ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
31961b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
3197c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
3198b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
319902494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
32004d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // Exceptions that have been synced shouldn't be deleted -- the sync
32014d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // adapter will take care of that -- but we want to "soft delete" them so
32024d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // that they will be removed from the instances list.
32034d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // TODO: this seems to confuse the sync adapter, and leaves you with an
32044d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       invisible "ghost" event after the server sync.  Maybe we can fix
32054d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       this by making instance generation smarter?  Not vital, since the
32064d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       exception instances disappear after the server sync.
32074d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //mDb.update(Tables.EVENTS, values, SQL_WHERE_ORIGINAL_ID_HAS_SYNC_ID,
32084d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //        selectionArgs);
32094d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
32104d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // It's possible for the original event to be on the server but have
32114d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // exceptions that aren't.  We want to remove all events with a matching
32124d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // original_id and an empty _sync_id.
32134d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID,
32144d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                            selectionArgs);
32154d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
321643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
321743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
3218b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
3219b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
3220b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
3221b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
3222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
3223b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
32241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
32251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
32261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
32271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
32281ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
32291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
32308f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
323110b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
3232420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3233dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
323410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
32351ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
32361ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
32371ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
32387e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
32391c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Delete rows from an Event-related table (e.g. Attendees) and mark corresponding events
32401c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * as dirty.
32411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
32427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
32437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
32447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
32457e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
32467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
32471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteFromEventRelatedTable(String table, Uri uri, String selection,
32481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            String[] selectionArgs) {
32491c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (table.equals(Tables.EVENTS)) {
32501c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            throw new IllegalArgumentException("Don't delete Events with this method "
32511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    + "(use deleteEventInternal)");
32521c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
32531c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues dirtyValues = new ContentValues();
32551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        dirtyValues.put(Events.DIRTY, "1");
32561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
32571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Re-issue the delete URI as a query.  Note that, if this is a by-ID request, the ID
32591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * will be in the URI, not selection/selectionArgs.
32601c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
32611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Note that the query will return data according to the access restrictions,
32621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * so we don't need to worry about deleting data we don't have permission to read.
32631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
32641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, GENERIC_EVENT_ID);
32657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
32667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
32671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long prevEventId = -1;
32681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
32691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long id = c.getLong(ID_INDEX);
32701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = c.getLong(EVENT_ID_INDEX);
32711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // Duplicate the event.  As a minor optimization, don't try to duplicate an
32721c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // event that we just duplicated on the previous iteration.
32731c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
32741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventId);
32751c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    prevEventId = eventId;
32761c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
32779ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDb.delete(table, SQL_WHERE_ID, new String[]{String.valueOf(id)});
32781c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
32791c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
32801c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            new String[] { String.valueOf(eventId)} );
32811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
32827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
32837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
32847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
32857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
32867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
32877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
32887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
32897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
32907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
32911c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Deletes rows from the Reminders table and marks the corresponding events as dirty.
32921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Ensures the hasAlarm column in the Event is updated.
32931c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
32941c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @return The number of rows deleted.
32951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
32961c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteReminders(Uri uri, boolean byId, String selection, String[] selectionArgs,
32971c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            boolean callerIsSyncAdapter) {
32981c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
32991c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this is a by-ID URI, make sure we have a good ID.  Also, confirm that the
33001c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * selection is null, since we will be ignoring it.
33011c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33021c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        long rowId = -1;
33031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
33041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (!TextUtils.isEmpty(selection)) {
33051c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
33061c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33071c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            rowId = ContentUris.parseId(uri);
33081c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (rowId < 0) {
33091c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
33101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33141c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Determine the set of events affected by this operation.  There can be multiple
33151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders with the same event_id, so to avoid beating up the database with "how many
33161c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders are left" and "duplicate this event" requests, we want to generate a list
33171c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * of affected event IDs and work off that.
33181c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
33191c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * TODO: use GROUP BY to reduce the number of rows returned in the cursor.  (The content
33201c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * provider query() doesn't take it as an argument.)
33211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        HashSet<Long> eventIdSet = new HashSet<Long>();
33231c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, new String[] { Attendees.EVENT_ID }, selection, selectionArgs, null);
33241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        try {
33251c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
33261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                eventIdSet.add(c.getLong(0));
33271c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33281c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        } finally {
33291c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            c.close();
33301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33331c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this isn't a sync adapter, duplicate each event (along with its associated tables),
33341c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * and mark each as "dirty".  This is for the benefit of partial-update sync.
33351c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33361c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (!callerIsSyncAdapter) {
33371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            ContentValues dirtyValues = new ContentValues();
33381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            dirtyValues.put(Events.DIRTY, "1");
33391c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33401c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Iterator<Long> iter = eventIdSet.iterator();
33411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (iter.hasNext()) {
33421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = iter.next();
33431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDbHelper.duplicateEvent(eventId);
33441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
33451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
33461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33491c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33501c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Issue the original deletion request.  If we were called with a by-ID URI, generate
33511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * a selection.
33521c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33531c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
33541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selection = SQL_WHERE_ID;
33551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
33561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int delCount = mDb.delete(Tables.REMINDERS, selection, selectionArgs);
33581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33601c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * For each event, set "hasAlarm" to zero if we've deleted the last of the reminders.
33611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * (If the event still has reminders, hasAlarm should already be 1.)  Because we're
33621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * executing in an exclusive transaction there's no risk of racing against other
33631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * database updates.
33641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33651c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues noAlarmValues = new ContentValues();
33661c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        noAlarmValues.put(Events.HAS_ALARM, 0);
33671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Iterator<Long> iter = eventIdSet.iterator();
33681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        while (iter.hasNext()) {
33691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long eventId = iter.next();
33701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            // Count up the number of reminders still associated with this event.
33721c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Cursor reminders = mDb.query(Tables.REMINDERS, new String[] { GENERIC_ID },
33731c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    SQL_WHERE_EVENT_ID, new String[] { String.valueOf(eventId) },
33741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    null, null, null);
33751c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            int reminderCount = reminders.getCount();
33761c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            reminders.close();
33771c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33781c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (reminderCount == 0) {
33791c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, noAlarmValues, SQL_WHERE_ID,
33801c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
33811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33821c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33831c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33841c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        return delCount;
33851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
33861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33871c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
338824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * Update rows in a table and, if this is a non-sync-adapter update, mark the corresponding
338924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * events as dirty.
339024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * <p>
339124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * This only works for tables that are associated with an event.  It is assumed that the
339224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * link to the Event row is a numeric identifier in a column called "event_id".
339324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     *
339424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param uri The original request URI.
339524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param byId Set to true if the URI is expected to include an ID.
339624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param updateValues The new values to apply.  Not all columns need be represented.
339724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selection For non-by-ID operations, the "where" clause to use.
339824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selectionArgs For non-by-ID operations, arguments to apply to the "where" clause.
339924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param callerIsSyncAdapter Set to true if the caller is a sync adapter.
340024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @return The number of rows updated.
34017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
340224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private int updateEventRelatedTable(Uri uri, String table, boolean byId,
340324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues updateValues, String selection, String[] selectionArgs,
340424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            boolean callerIsSyncAdapter)
340524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    {
340624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
340724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Confirm that the request has either an ID or a selection, but not both.  It's not
340824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * actually "wrong" to have both, but it's not useful, and having neither is likely
340924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * a mistake.
341024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         *
341124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * If they provided an ID in the URI, convert it to an ID selection.
341224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
341324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (byId) {
341424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!TextUtils.isEmpty(selection)) {
341524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
341624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
341724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            long rowId = ContentUris.parseId(uri);
341824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (rowId < 0) {
341924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
342024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
342124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selection = SQL_WHERE_ID;
342224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
342324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        } else {
342424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (TextUtils.isEmpty(selection)) {
342524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection is required for " + uri);
342624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
342724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
342824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
342924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
343024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Query the events to update.  We want all the columns from the table, so we us a
343124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * null projection.
343224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
343324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Cursor c = mDb.query(table, null /*projection*/, selection, selectionArgs,
343424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                null, null, null);
34357e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
34367e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
343724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (c.getCount() == 0) {
343824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                Log.d(TAG, "No query results for " + uri + ", selection=" + selection +
343924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        " selectionArgs=" + Arrays.toString(selectionArgs));
344024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return 0;
344124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
344224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
344324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues dirtyValues = null;
344424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!callerIsSyncAdapter) {
344524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues = new ContentValues();
344624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues.put(Events.DIRTY, "1");
344724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
344824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
344924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int idIndex = c.getColumnIndex(GENERIC_ID);
345024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int eventIdIndex = c.getColumnIndex(GENERIC_EVENT_ID);
345124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (idIndex < 0 || eventIdIndex < 0) {
345224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new RuntimeException("Lookup on _id/event_id failed for " + uri);
345324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
345424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
345524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            /*
345624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * For each row found:
345724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - merge original values with update values
345824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update database
345924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - if not sync adapter, set "dirty" flag in corresponding event to 1
346024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update Event attendee status
346124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             */
346224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            while (c.moveToNext()) {
346324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /* copy the original values into a ContentValues, then merge the changes in */
346424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                ContentValues values = new ContentValues();
346524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                DatabaseUtils.cursorRowToContentValues(c, values);
346624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                values.putAll(updateValues);
346724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
346824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long id = c.getLong(idIndex);
346924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long eventId = c.getLong(eventIdIndex);
347024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
347124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    // Make a copy of the original, so partial-update code can see diff.
347224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDbHelper.duplicateEvent(eventId);
347324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
347424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                mDb.update(table, values, SQL_WHERE_ID, new String[] { String.valueOf(id) });
347524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
347624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
347724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                            new String[] { String.valueOf(eventId) });
347824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
34797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
348024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
348124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /*
348224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * The Events table has a "selfAttendeeStatus" field that usually mirrors the
348324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * "attendeeStatus" column of one row in the Attendees table.  It's the provider's
348424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * job to keep these in sync, so we have to check for changes here.  (We have
348524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * to do it way down here because this is the only point where we have the
348624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * merged Attendees values.)
348724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
348824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * It's possible, but not expected, to have multiple Attendees entries with
348924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * matching attendeeEmail.  The behavior in this case is not defined.
349024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
349124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * We could do this more efficiently for "bulk" updates by caching the Calendar
349224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * owner email and checking it here.
349324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 */
349424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (table.equals(Tables.ATTENDEES)) {
349524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    updateEventAttendeeStatus(mDb, values);
349624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
34977e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
34987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
34997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
35007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
35017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
35027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
35037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
35042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int deleteMatchingColors(String selection, String[] selectionArgs) {
35052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // query to find all the colors that match, for each
35062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - verify no one references it
35072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - delete color
35082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs, null,
35092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                null, null);
35102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (c == null) {
35112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            return 0;
35122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
35132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
35142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            Cursor c2 = null;
35152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            while (c.moveToNext()) {
35162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String index = c.getString(COLORS_COLOR_INDEX_INDEX);
35172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = c.getString(COLORS_ACCOUNT_NAME_INDEX);
35182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = c.getString(COLORS_ACCOUNT_TYPE_INDEX);
35192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                boolean isCalendarColor = c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
35202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
35212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (isCalendarColor) {
35222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = mDb.query(Tables.CALENDARS, ID_ONLY_PROJECTION,
35232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                SQL_WHERE_CALENDAR_COLOR, new String[] {
35242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                        accountName, accountType, index
35252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                }, null, null, null);
35262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
35272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
35282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " calendars.");
35292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
35302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
35312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
35322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = query(Events.CONTENT_URI, ID_ONLY_PROJECTION, SQL_WHERE_EVENT_COLOR,
35332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                new String[] {accountName, accountType, index}, null);
35342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
35352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
35362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " events.");
35372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
35382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
35392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
35402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
35412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c2 != null) {
35422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2.close();
35432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
35442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
35452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
35462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
35472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
35482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
35492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
35502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
35512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return mDb.delete(Tables.COLORS, selection, selectionArgs);
35522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
35532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
355474ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio    private int deleteMatchingCalendars(String selection, String[] selectionArgs) {
35559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
35569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
35579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
355874ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, selection,
355974ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                selectionArgs,
356074ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* groupBy */,
356174ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* having */,
356274ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* sortOrder */);
35639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
35649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
35659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
35679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
35689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
35699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
35709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
35719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
35729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
35739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
357474ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        return mDb.delete(Tables.CALENDARS, selection, selectionArgs);
35759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3577fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
3578fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
3579fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3580fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
3581fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3582fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
3583fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3584fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
3585fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
3586fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
3587fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3588fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3589fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
3590fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
3591fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3592fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
3593fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
3594fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
3595fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3596fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
3597fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
3598fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
3599d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean doesStatusCancelUpdateMeanUpdate(ContentValues values,
3600d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues modValues) {
3601d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean isStatusCanceled = modValues.containsKey(Events.STATUS) &&
3602d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                (modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
3603fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
3604d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID);
3605d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3606d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!TextUtils.isEmpty(originalSyncId)) {
3607d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // This event is an exception.  See if the recurring event still exists.
3608d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                return doesEventExistForSyncId(originalSyncId);
3609d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3610d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3611d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // This is the normal case, we just want an UPDATE
3612d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return true;
3613d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
3614d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
36152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int handleUpdateColors(ContentValues values, String selection, String[] selectionArgs) {
36162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
36172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int result = mDb.update(Tables.COLORS, values, selection, selectionArgs);
36182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (values.containsKey(Colors.COLOR)) {
36192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            try {
36202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs,
36212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        null /* groupBy */, null /* having */, null /* orderBy */);
36222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                while (c.moveToNext()) {
36232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    boolean calendarColor =
36242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
36252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = c.getInt(COLORS_COLOR_INDEX);
36262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String[] args = {
36272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_NAME_INDEX),
36282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_TYPE_INDEX),
36292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_COLOR_INDEX_INDEX)
36302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    };
36312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ContentValues colorValue = new ContentValues();
36322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (calendarColor) {
36332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Calendars.CALENDAR_COLOR, color);
3634d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.CALENDARS, colorValue, SQL_WHERE_CALENDAR_COLOR, args);
36352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
36362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Events.EVENT_COLOR, color);
3637d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.EVENTS, colorValue, SQL_WHERE_EVENT_COLOR, args);
36382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
36392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
36402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            } finally {
36412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (c != null) {
36422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    c.close();
36432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
36442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
36452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
36462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return result;
36472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
36482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
3649d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3650d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
3651d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Handles a request to update one or more events.
3652d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
3653d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * The original event(s) will be loaded from the database, merged with the new values,
3654d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * and the result checked for validity.  In some cases this will alter the supplied
3655d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * arguments (e.g. zeroing out the times on all-day events), change additional fields (e.g.
3656d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * update LAST_DATE when DTSTART changes), or cause modifications to other tables (e.g. reset
3657d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Instances when a recurrence rule changes).
3658d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
3659d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param cursor The set of events to update.
36604b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden     * @param updateValues The changes to apply to each event.
3661d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param callerIsSyncAdapter Indicates if the request comes from the sync adapter.
3662d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the number of rows updated
3663d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
36644b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden    private int handleUpdateEvents(Cursor cursor, ContentValues updateValues,
3665d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean callerIsSyncAdapter) {
3666d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        /*
36671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * This field is considered read-only.  It should not be modified by applications or
36681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * by the sync adapter.
36691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
36701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        updateValues.remove(Events.HAS_ALARM);
36711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
36721c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
3673d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For a single event, we can just load the event, merge modValues in, perform any
3674d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * fix-ups (putting changes into modValues), check validity, and then update().  We have
3675d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * to be careful that our fix-ups don't confuse the sync adapter.
3676d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         *
3677d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For multiple events, we need to load, merge, and validate each event individually.
3678d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * If no single-event-specific changes need to be made, we could just issue the original
3679d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * bulk update, which would be more efficient than a series of individual updates.
3680d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * However, doing so would prevent us from taking advantage of the partial-update
3681d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * mechanism.
3682d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         */
3683d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (cursor.getCount() > 1) {
3684d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (Log.isLoggable(TAG, Log.DEBUG)) {
3685d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG, "Performing update on " + cursor.getCount() + " events");
3686d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3687d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3688d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        while (cursor.moveToNext()) {
36899f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Make a copy of updateValues so we can make some local changes.
36904b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden            ContentValues modValues = new ContentValues(updateValues);
36919f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
36929f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Load the event into a ContentValues object.
3693d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues values = new ContentValues();
3694d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
36959f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            boolean doValidate = false;
36969f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (!callerIsSyncAdapter) {
36979f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                try {
36989f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // Check to see if the data in the database is valid.  If not, we will skip
36999f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // validation of the update, so that we don't blow up on attempts to
37009f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // modify existing badly-formed events.
37019f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    validateEventData(values);
37029f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    doValidate = true;
37039f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                } catch (IllegalArgumentException iae) {
37049f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    Log.d(TAG, "Event " + values.getAsString(Events._ID) +
37059f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            " malformed, not validating update (" +
37069f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            iae.getMessage() + ")");
37079f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                }
37089f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
37099f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
37109f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Merge the modifications in.
3711d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            values.putAll(modValues);
3712d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
37132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If a color_index is being set make sure it's valid
3714387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_id = modValues.getAsString(Events.EVENT_COLOR_KEY);
37152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_id)) {
37162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
37172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
37182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = mDb.query(Tables.CALENDARS, ACCOUNT_PROJECTION, SQL_WHERE_ID,
37192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        new String[] { values.getAsString(Events.CALENDAR_ID) }, null, null, null);
37202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
37212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.moveToFirst()) {
37222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = c.getString(ACCOUNT_NAME_INDEX);
37232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = c.getString(ACCOUNT_TYPE_INDEX);
37242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
37252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
37262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null) {
37272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
37282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
37292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
37302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_id, Colors.TYPE_EVENT);
37312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
37322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
37339f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Scrub and/or validate the combined event.
3734be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            if (callerIsSyncAdapter) {
3735be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                scrubEventData(values, modValues);
37369f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
37379f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (doValidate) {
3738be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                validateEventData(values);
3739be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            }
3740d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3741d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Look for any updates that could affect LAST_DATE.  It's defined as the end of
3742d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // the last meeting, so we need to pay attention to DURATION.
3743d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.DTSTART) ||
3744d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DTEND) ||
3745d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DURATION) ||
3746d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EVENT_TIMEZONE) ||
3747d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RRULE) ||
3748d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RDATE) ||
3749d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXRULE) ||
3750d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXDATE)) {
3751d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long newLastDate;
3752d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3753d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    newLastDate = calculateLastDate(values);
3754d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } catch (DateException de) {
3755d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    throw new IllegalArgumentException("Unable to compute LAST_DATE", de);
3756d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3757d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Long oldLastDateObj = values.getAsLong(Events.LAST_DATE);
3758d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long oldLastDate = (oldLastDateObj == null) ? -1 : oldLastDateObj;
3759d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (oldLastDate != newLastDate) {
3760d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // This overwrites any caller-supplied LAST_DATE.  This is okay, because the
3761d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // caller isn't supposed to be messing with the LAST_DATE field.
3762d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (newLastDate < 0) {
3763d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.putNull(Events.LAST_DATE);
3764d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    } else {
3765d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.put(Events.LAST_DATE, newLastDate);
3766fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3767fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3768d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3769d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3770d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!callerIsSyncAdapter) {
3771d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DIRTY, 1);
3772d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3773fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3774d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Disallow updating the attendee status in the Events
3775d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // table.  In the future, we could support this but we
3776d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // would have to query and update the attendees table
3777d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // to keep the values consistent.
3778d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
3779d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Updating "
3780d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + Events.SELF_ATTENDEE_STATUS
3781d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + " in Events table is not allowed.");
3782d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3783d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3784d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (fixAllDayTime(values, modValues)) {
3785d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (Log.isLoggable(TAG, Log.WARN)) {
3786d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    Log.w(TAG, "handleUpdateEvents: " +
3787d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            "allDay is true but sec, min, hour were not 0.");
3788fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3789d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3790d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3791d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // For taking care about recurrences exceptions cancelations, check if this needs
3792d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            //  to be an UPDATE or a DELETE
3793d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean isUpdate = doesStatusCancelUpdateMeanUpdate(values, modValues);
3794d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3795d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            long id = values.getAsLong(Events._ID);
3796d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3797d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (isUpdate) {
3798d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // If a user made a change, possibly duplicate the event so we can do a partial
3799d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // update. If a sync adapter made a change and that change marks an event as
3800d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // un-dirty, remove any duplicates that may have been created earlier.
3801d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (!callerIsSyncAdapter) {
3802d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mDbHelper.duplicateEvent(id);
3803d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } else {
3804d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DIRTY)
3805d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            && modValues.getAsInteger(Events.DIRTY) == 0) {
3806d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mDbHelper.removeDuplicateEvent(id);
3807d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3808d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3809d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int result = mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
3810d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        new String[] { String.valueOf(id) });
3811d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (result > 0) {
3812d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    updateEventRawTimesLocked(id, modValues);
3813d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mInstancesHelper.updateInstancesLocked(modValues, id,
3814d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            false /* not a new event */, mDb);
3815d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3816d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // XXX: should we also be doing this when RRULE changes (e.g. instances
3817d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    //      are introduced or removed?)
3818d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DTSTART) ||
3819d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            modValues.containsKey(Events.STATUS)) {
3820d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // If this is a cancellation knock it out
3821d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // of the instances table
3822d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (modValues.containsKey(Events.STATUS) &&
3823d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED) {
3824d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            String[] args = new String[] {String.valueOf(id)};
3825d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, args);
3826d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3827d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3828d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // The start time or status of the event changed, so run the
3829d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // event alarm scheduler.
3830d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (Log.isLoggable(TAG, Log.DEBUG)) {
3831d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            Log.d(TAG, "updateInternal() changing event");
3832d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3833d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3834d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3835d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3836d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    sendUpdateNotification(id, callerIsSyncAdapter);
3837d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3838d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else {
3839d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3840d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3841d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                sendUpdateNotification(callerIsSyncAdapter);
3842fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3843fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3844d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3845d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return cursor.getCount();
3846fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3847fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
38489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
38499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
3850b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
3851ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
38529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
38539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38548d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
38550739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
38560739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
38570739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
38589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
38609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
38619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
38628d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
38638d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                                Calendars.ACCOUNT_TYPE), selectionArgs);
38649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
38668d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
38678d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
38682ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
3869dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
38709323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3871dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3872dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3873dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
38749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
38759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38762f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
38772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Integer color = values.getAsInteger(Colors.COLOR);
38782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (values.size() > 1 || (values.size() == 1 && color == null)) {
38792f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new UnsupportedOperationException("You may only change the COLOR "
38802f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            + "for an existing Colors entry.");
38812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
38828d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                return handleUpdateColors(values, appendAccountToSelection(uri, selection,
38838d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE),
38842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
38852f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
388643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
38879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
38889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
388943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
389043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
389143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
389243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
389343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
389443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
389543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
389643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
389743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
38984cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    if (selection != null && TextUtils.equals(selection,"_id=?")) {
38994cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                        id = Long.parseLong(selectionArgs[0]);
39004cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
390143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
390243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
390343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
390443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
3905b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
390643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
390743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
390843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
3909c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Calendars.DIRTY, 1);
39102fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
39119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
39129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
39139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
39149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3915387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
39162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
39172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
39182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
39192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
39202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        Account account = getAccount(id);
39212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (account != null) {
39222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountName = account.name;
39232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountType = account.type;
39242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
39252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
39262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    verifyColorExists(accountName, accountType, color_id, Colors.TYPE_CALENDAR);
39272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
39289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3929b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
3930636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
39319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39323ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
3933d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
39344067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                    if (values.containsKey(Calendars.VISIBLE)) {
3935d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
3936d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
3937d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
3938d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
3939420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        mCalendarAlarm.scheduleNextAlarm(false);
3940d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
39413ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
3942dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
39433ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
39443ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
39459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
39469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
39489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
39499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
3950d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Cursor events = null;
39519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3952d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // Grab the full set of columns for each selected event.
3953d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: define a projection with just the data we need (e.g. we don't need to
3954d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                //       validate the SYNC_* columns)
39559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3956d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3957d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (match == EVENTS_ID) {
3958d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // Single event, identified by ID.
3959d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        long id = ContentUris.parseId(uri);
3960d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3961d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                SQL_WHERE_ID, new String[] { String.valueOf(id) },
3962d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
39639ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    } else {
3964d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // One or more events, identified by the selection / selectionArgs.
3965d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
3966d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                selection, selectionArgs,
3967d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
39689ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    }
396906c305d35741db303bd3aacd0eab5af8de0ab34eErik
3970d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events.getCount() == 0) {
397124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        Log.i(TAG, "No events to update: uri=" + uri + " selection=" + selection +
3972d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                " selectionArgs=" + Arrays.toString(selectionArgs));
3973d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        return 0;
3974d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
39753ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3976d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    return handleUpdateEvents(events, values, callerIsSyncAdapter);
3977d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } finally {
3978d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events != null) {
3979d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events.close();
3980fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
39819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
39829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
398324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES:
398424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, false, values, selection,
398524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
398624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES_ID:
398724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, true, values, null, null,
398824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
39899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39902fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
39912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
39922fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
39939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3994b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
3995636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
39969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39972fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
39982fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
39992fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
4000b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
40019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
400224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
400324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case REMINDERS:
400424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.REMINDERS, false, values, selection,
400524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
40062fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
400724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                int count = updateEventRelatedTable(uri, Tables.REMINDERS, true, values, null, null,
400824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
40097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
40109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
40119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
40129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
40139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
40149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4015420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
40167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
40179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
401824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
401924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case EXTENDED_PROPERTIES_ID:
402024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.EXTENDED_PROPERTIES, true, values,
402124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        null, null, callerIsSyncAdapter);
402224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
402383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
402483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
402583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
4026420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false);
402783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
402883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
402983512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
4030420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(true);
403183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
403283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
40339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4034315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
4035315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
4036315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
4037315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4038315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4039315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
4040315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4041315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
4042315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
4043315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
4044315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4045315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4046315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
4047315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
4048315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4049315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
4050315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
4051b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
4052315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4053315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
4054f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "home", set the Instances
4055f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the previous
4056f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "auto", set the Instances
4057f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the current
4058f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // device one
4059f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone Instances is set AND if we are in "home"
4060f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone type, then save the timezone Instance into
4061f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // "previous" too
4062315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
4063315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
4064315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
4065315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
4066315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
4067315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
4068315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
4069315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
4070315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
4071315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
4072315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
4073315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4074315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
4075d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
4076315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
4077315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4078d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4079315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4080315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4081315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
4082315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
4083315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
4084315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
4085315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
4086315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4087d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4088315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4089315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4090315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4091315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4092315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
4093315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
4094315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
4095315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
4096315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
4097315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
4098315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
4099315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
4100d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
4101315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
4102315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
4103315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
4104d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
4105315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4106315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4107315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4108315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4109315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
4110315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
4111315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
41129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
41139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
41149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
41159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    /**
41182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * Verifies that a color with the given index exists for the given Calendar
41192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * entry.
41202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     *
41212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountName The email of the account the color is for
41222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountType The type of account the color is for
41234755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorIndex The color_index being set for the calendar
41244755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorType The type of color expected (Calendar/Event)
41252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @return The color specified by the index
41262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     */
41274755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private int verifyColorExists(String accountName, String accountType, String colorIndex,
41284755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            int colorType) {
41292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
41302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            throw new IllegalArgumentException("Cannot set color. A valid account does"
41312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    + " not exist for this calendar.");
41322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
41332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int color;
41342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
41352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
41364755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
41374755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            if (!c.moveToFirst()) {
41384755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                throw new IllegalArgumentException("Color type: " + colorType + " and index "
41394755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        + colorIndex + " does not exist for account.");
41402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
41412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            color = c.getInt(COLORS_COLOR_INDEX);
41422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
41432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
41442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
41452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
41462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
41472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return color;
41482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
41492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
41509ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendLastSyncedColumnToSelection(String selection, Uri uri) {
41519ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (getIsCallerSyncAdapter(uri)) {
41529ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
4153595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
41549ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final StringBuilder sb = new StringBuilder();
4155b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sb.append(CalendarContract.Events.LAST_SYNCED).append(" = 0");
41569ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return appendSelection(sb, selection);
4157595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
4158595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
41598d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private String appendAccountToSelection(
41608d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            Uri uri,
41618d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String selection,
41628d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String accountNameColumn,
41638d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String accountTypeColumn) {
41640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountName = QueryParameterUtils.getQueryParameter(uri,
4165b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
41660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountType = QueryParameterUtils.getQueryParameter(uri,
4167b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
41680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (!TextUtils.isEmpty(accountName)) {
41698d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            final StringBuilder sb = new StringBuilder()
41708d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(accountNameColumn)
41718d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append("=")
41728d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(DatabaseUtils.sqlEscapeString(accountName))
41738d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(" AND ")
41748d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(accountTypeColumn)
41758d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append("=")
41768d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(DatabaseUtils.sqlEscapeString(accountType));
41778d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            return appendSelection(sb, selection);
41789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
41799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
41809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
41819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
41829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
41839ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendSelection(StringBuilder sb, String selection) {
41849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (!TextUtils.isEmpty(selection)) {
41859ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(" AND (");
41869ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(selection);
41879ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(')');
41889ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
41899ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return sb.toString();
41909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
41919ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
41920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    /**
41930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * Verifies that the operation is allowed and throws an exception if it
41940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * isn't. This defines the limits of a sync adapter call vs an app call.
4195683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * <p>
4196683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * Also rejects calls that have a selection but shouldn't, or that don't have a selection
4197683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * but should.
4198c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik     *
41990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param type The type of call, {@link #TRANSACTION_QUERY},
42000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_INSERT}, {@link #TRANSACTION_UPDATE}, or
42010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_DELETE}
42020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param uri
42030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param values
42040739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param isSyncAdapter
42050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     */
42060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyTransactionAllowed(int type, Uri uri, ContentValues values,
42070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            boolean isSyncAdapter, int uriMatch, String selection, String[] selectionArgs) {
4208f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Queries are never restricted to app- or sync-adapter-only, and we don't
4209f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // restrict the set of columns that may be accessed.
4210f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        if (type == TRANSACTION_QUERY) {
4211f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            return;
4212f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4213f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
4214683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        if (type == TRANSACTION_UPDATE || type == TRANSACTION_DELETE) {
42152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // TODO review this list, document in contract.
4216683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            if (!TextUtils.isEmpty(selection)) {
4217683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Only allow selections for the URIs that can reasonably use them.
42182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Whitelist of URIs allowed selections
4219683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4220683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case SYNCSTATE:
4221683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDARS:
4222683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4223683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4224683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDAR_ALERTS:
4225683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4226683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EXTENDED_PROPERTIES:
4227683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
42282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    case COLORS:
4229683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4230683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4231683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection not permitted for " + uri);
4232683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4233683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            } else {
4234683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Disallow empty selections for some URIs.
42352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Blacklist of URIs _not_ allowed empty selections
4236683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4237683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4238683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4239683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4240683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
4241683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection must be specified for "
4242683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                                + uri);
4243683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4244683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4245683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4246683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            }
4247683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        }
4248683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden
4249f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Only the sync adapter can use these to make changes.
42502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (!isSyncAdapter) {
42512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            switch (uriMatch) {
42522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE:
42532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE_ID:
42542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES:
42552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES_ID:
42562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case COLORS:
42572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Only sync adapters may write using " + uri);
42582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                default:
42592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    break;
4260f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            }
4261f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4262f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
42630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (type) {
42640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_INSERT:
42650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException(
42670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                            "Inserting into instances not supported");
42680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4269c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4270c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
42710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
42740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
42750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
42760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
42770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
42790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_UPDATE:
42800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Updating instances not supported");
42820739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4283c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4284c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
42850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
42880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
42890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
42900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
42910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
42930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_DELETE:
42940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
42950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Deleting instances not supported");
42960739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
42970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
42980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
42990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
43000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
43010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
43020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
43040739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
43050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyHasAccount(Uri uri, String selection, String[] selectionArgs) {
4306c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String accountName = QueryParameterUtils.getQueryParameter(uri, Calendars.ACCOUNT_NAME);
43070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String accountType = QueryParameterUtils.getQueryParameter(uri,
4308c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Calendars.ACCOUNT_TYPE);
43090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
43100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (selection != null && selection.startsWith(ACCOUNT_SELECTION_PREFIX)) {
43110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountName = selectionArgs[0];
43120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountType = selectionArgs[1];
43130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
43140739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43150739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
43160739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            throw new IllegalArgumentException(
43170739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    "Sync adapters must specify an account and account type: " + uri);
43180739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43190739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
43200739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
4321c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private void verifyColumns(ContentValues values, int uriMatch) {
4322c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
4323c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            return;
4324c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4325c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String[] columns;
4326c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        switch (uriMatch) {
4327c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4328c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4329c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4330c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4331c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = Events.PROVIDER_WRITABLE_COLUMNS;
4332c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4333c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            default:
4334c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = PROVIDER_WRITABLE_DEFAULT_COLUMNS;
4335c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4336c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4337c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
4338c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        for (int i = 0; i < columns.length; i++) {
4339c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            if (values.containsKey(columns[i])) {
4340c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                throw new IllegalArgumentException("Only the provider may write to " + columns[i]);
4341c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            }
4342c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4343c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    }
4344c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
43450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyNoSyncColumns(ContentValues values, int uriMatch) {
4346c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
43470739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return;
43480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String[] syncColumns;
43500739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (uriMatch) {
43510739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS:
43520739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS_ID:
43530739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES:
43540739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES_ID:
4355c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Calendars.SYNC_WRITABLE_COLUMNS;
4356c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4357c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4358c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4359c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4360c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4361c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Events.SYNC_WRITABLE_COLUMNS;
43620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
43630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            default:
43640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                syncColumns = SYNC_WRITABLE_DEFAULT_COLUMNS;
43650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
43660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
43670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        for (int i = 0; i < syncColumns.length; i++) {
43690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (values.containsKey(syncColumns[i])) {
43700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                throw new IllegalArgumentException("Only sync adapters may write to "
43710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                        + syncColumns[i]);
43720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
43730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
43750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
43769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
43779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
43789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
43799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
4380c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                new String[] {Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
4381fa332ecedc0c340109811552407142f6e4f600b2RoboErik                        Calendars.CAL_SYNC1, Calendars.SYNC_EVENTS},
43829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
43839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
43849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
43859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
43869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
43879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
43889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
4389ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
43909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
4391ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
4392ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
4393ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
4394ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
4395ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
4396ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
4397ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
43989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
43992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (cursor != null)
44002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.close();
44019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
44029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
44039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
44049535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
44059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
4406f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
4407f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
4408f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
4409f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
44109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
44119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
44129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
44139535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
44149535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
44159535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
44169535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
44179535627bf6295cd94447beb83e1aac41f50c3600Erik        }
44189535627bf6295cd94447beb83e1aac41f50c3600Erik
44199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
44209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
44219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
44229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
44239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
44249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
44259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
44269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
44279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
44289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4429a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4430a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4431a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4432a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
4433dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
44349ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4435a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4436dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
4437dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
4438dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
4439a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4440a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4441a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4442a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4443a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4444a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
4445a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
4446a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
4447a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
4448a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
4449a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
44509ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param eventId the ID of the event that changed, or -1 for no specific event
44519ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4452a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4453dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
4454dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
4455a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
4456a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
4457a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
4458a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
4459a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
4460dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
4461dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
4462dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
4463dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
4464dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
4465dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
4466dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
4467dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
4468dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
4469dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
4470dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
4471dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
4472dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
4473dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
4474dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
4475dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
4476dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
4477a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
4478dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
4479a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4480a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4481a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4482a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
4483a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
44849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #sendUpdateNotification(boolean)} instead.
4485a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
44869ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @see #sendUpdateNotification(boolean)
4487a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4488a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
4489a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
4490b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.CONTENT_URI);
4491f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
4492f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
4493f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
4494e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
4495a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4496a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
44970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_QUERY = 0;
44980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_INSERT = 1;
44990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_UPDATE = 2;
45000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_DELETE = 3;
45010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
45020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:off
45030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final String[] SYNC_WRITABLE_DEFAULT_COLUMNS = new String[] {
4504b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars.DIRTY,
4505b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars._SYNC_ID
45060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    };
4507c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String[] PROVIDER_WRITABLE_DEFAULT_COLUMNS = new String[] {
4508c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    };
45090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:on
45100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
45119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
45129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
45139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
45142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS = 4;
45152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS_ID = 5;
45162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES = 6;
45172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES_ID = 7;
45182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS = 8;
45192ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS_ID = 9;
45202ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES = 10;
45212ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES_ID = 11;
45222ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS = 12;
45232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_ID = 13;
45242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_BY_INSTANCE = 14;
45252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_BY_DAY = 15;
45262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE = 16;
45272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE_ID = 17;
45282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES = 18;
45292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES_ID = 19;
45302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_DAYS = 20;
45312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM = 21;
45322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM_REMOVE = 22;
45332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int TIME = 23;
45342ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES = 24;
45352ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES_ID = 25;
45362ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH = 26;
45372ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH_BY_DAY = 27;
45382ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int PROVIDER_PROPERTIES = 28;
4539bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID = 29;
4540bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID2 = 30;
45413b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static final int EMMA = 31;
45422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS = 32;
45439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
45459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
45462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final HashMap<String, String> sColorsProjectionMap;
4547f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final HashMap<String, String> sEventsProjectionMap;
454819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
45499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
45509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
45519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
4552315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
455339c65e5716e21e863d8de587d139dae85f99422fFred Quintana    private static final HashMap<String, String> sCountProjectionMap;
45549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
4556b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/when/*/*", INSTANCES);
4557b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
4558b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
4559b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/searchbyday/*/*/*",
456081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
4561b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
4562b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events", EVENTS);
4563b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events/#", EVENTS_ID);
4564b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities", EVENT_ENTITIES);
4565b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
4566b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS);
4567b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS_ID);
4568b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
4569b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
4570b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees", ATTENDEES);
4571b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees/#", ATTENDEES_ID);
4572b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders", REMINDERS);
4573b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders/#", REMINDERS_ID);
4574b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
4575b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties/#",
4576b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                EXTENDED_PROPERTIES_ID);
4577b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
4578b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
4579b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/by_instance",
4580b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
4581b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate", SYNCSTATE);
4582b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
4583b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, CalendarAlarmManager.SCHEDULE_ALARM_PATH,
4584420b7fb569773ae573fbe90c3a9c522d4c368863Erik                SCHEDULE_ALARM);
4585b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY,
4586b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
4587b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time/#", TIME);
4588b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time", TIME);
4589b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "properties", PROVIDER_PROPERTIES);
4590b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#", EXCEPTION_ID);
4591b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#/#", EXCEPTION_ID2);
45923b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        sUriMatcher.addURI(CalendarContract.AUTHORITY, "emma", EMMA);
45932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "colors", COLORS);
45949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
459539c65e5716e21e863d8de587d139dae85f99422fFred Quintana        /** Contains just BaseColumns._COUNT */
459639c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap = new HashMap<String, String>();
459739c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
459839c65e5716e21e863d8de587d139dae85f99422fFred Quintana
45992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap = new HashMap<String, String>();
46002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors._ID, Colors._ID);
46012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.DATA, Colors.DATA);
46022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_NAME, Colors.ACCOUNT_NAME);
46032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_TYPE, Colors.ACCOUNT_TYPE);
4604387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sColorsProjectionMap.put(Colors.COLOR_KEY, Colors.COLOR_KEY);
46052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR_TYPE, Colors.COLOR_TYPE);
46062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR, Colors.COLOR);
46072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
46089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
46099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
461002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_NAME, Events.ACCOUNT_NAME);
461102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_TYPE, Events.ACCOUNT_TYPE);
4612c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.TITLE, Events.TITLE);
4613c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4614c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4615c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.STATUS, Events.STATUS);
461602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4617387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR_KEY, Events.EVENT_COLOR_KEY);
4618c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4619c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTSTART, Events.DTSTART);
4620c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTEND, Events.DTEND);
4621c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4622c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4623c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DURATION, Events.DURATION);
4624c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4625c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4626c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4627c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4628c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, Events.HAS_EXTENDED_PROPERTIES);
4629c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RRULE, Events.RRULE);
4630c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RDATE, Events.RDATE);
4631c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
4632c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
4633c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
463434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4635c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
4636c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4637c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4638c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4639c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4640c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, Events.GUESTS_CAN_INVITE_OTHERS);
4641c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4642c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4643c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4644c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventsProjectionMap.put(Events.CUSTOM_APP_PACKAGE, Events.CUSTOM_APP_PACKAGE);
4645c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventsProjectionMap.put(Events.CUSTOM_APP_URI, Events.CUSTOM_APP_URI);
4646c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DELETED, Events.DELETED);
464702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
46489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4649e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
46501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
46511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
46521ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
46539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
4654c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
4655387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR_KEY);
465602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CALENDAR_ACCESS_LEVEL);
4657c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
465802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
4659c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
466002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
466102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
46622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap
46632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                .put(Calendars.ALLOWED_ATTENDEE_TYPES, Calendars.ALLOWED_ATTENDEE_TYPES);
46642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_AVAILABILITY, Calendars.ALLOWED_AVAILABILITY);
466502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
466602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND, Calendars.CAN_ORGANIZER_RESPOND);
466702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
4668c339afc7df041ebfc5f4587f78cf38562aa23459Alon Albert        sEventsProjectionMap.put(Events.DISPLAY_COLOR, Events.DISPLAY_COLOR);
46699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4670982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
4671e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
4672e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
4673982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4674e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4675982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
4676c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events._ID, Events._ID);
467702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
467802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
467902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
468002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
468102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
468202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
46839ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
468402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
468502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
468602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
468702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
468802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
468902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
469002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
469102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
469202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
469302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
469402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
469502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
469602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
4697c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DIRTY, Events.DIRTY);
46989ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
46999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
470046f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
4701c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.TITLE, Events.TITLE);
4702c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4703c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4704c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.STATUS, Events.STATUS);
470502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4706c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4707c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTSTART, Events.DTSTART);
4708c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTEND, Events.DTEND);
4709c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4710c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4711c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DURATION, Events.DURATION);
4712c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4713c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4714c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4715c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4716c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES,
4717c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.HAS_EXTENDED_PROPERTIES);
4718c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RRULE, Events.RRULE);
4719c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RDATE, Events.RDATE);
4720c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
4721c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
4722c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
472334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4724c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
4725c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.ORIGINAL_INSTANCE_TIME);
4726c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4727c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4728c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4729c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4730c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS,
4731c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.GUESTS_CAN_INVITE_OTHERS);
4732c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4733c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4734c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4735c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventEntitiesProjectionMap.put(Events.CUSTOM_APP_PACKAGE, Events.CUSTOM_APP_PACKAGE);
4736c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventEntitiesProjectionMap.put(Events.CUSTOM_APP_URI, Events.CUSTOM_APP_URI);
4737c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DELETED, Events.DELETED);
473819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
473919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
474002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
474102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
474202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
474302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
474402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
474502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
47469ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
474702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
474802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
474902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
4750470aa5bc291ca33d51dda356f38ac2954026da9aAlon Albert        sEventEntitiesProjectionMap.put(Events.DIRTY, Events.DIRTY);
47519ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
4752fa332ecedc0c340109811552407142f6e4f600b2RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
475302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
475402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
475502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
475602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
475702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
475802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
475902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
476002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
476102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
476219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
47639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
47641b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
47659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
47669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
47679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
47689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
47699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
47709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
47719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
47729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
47739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
47759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
47769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
47779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
47789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
47799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
47809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
47819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
4782bafe9de156292f65b1079dd1eb586669f573d9e6Michael Chan        sAttendeesProjectionMap.put(Attendees.ATTENDEE_IDENTITY, "attendeeIdentity");
4783bafe9de156292f65b1079dd1eb586669f573d9e6Michael Chan        sAttendeesProjectionMap.put(Attendees.ATTENDEE_ID_NAMESPACE, "attendeeIdNamespace");
478402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
478502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
47869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
47889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
47899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
47909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
47919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
4792361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
4793361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
47949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
47959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
47969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
47979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
47989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
47999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
48009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
48019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
48029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4803315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4804315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4805315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4806315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4807315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
48089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
48099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
481064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
48119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
481264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * This is called by AccountManager when the set of accounts is updated.
481364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * <p>
481464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * We are overriding this since we need to delete from the
48159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
48167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
48177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
481864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     *
481964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * @param accounts The list of currently active accounts.
48209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4821f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    @Override
48229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
482364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        Thread thread = new AccountsUpdatedThread(accounts);
482464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        thread.start();
482564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
482664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
482764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private class AccountsUpdatedThread extends Thread {
482864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        private Account[] mAccounts;
482964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
483064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountsUpdatedThread(Account[] accounts) {
483164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            mAccounts = accounts;
483264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
483364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
483464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        @Override
483564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        public void run() {
483664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // The process could be killed while the thread runs.  Right now that isn't a problem,
483764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // because we'll just call removeStaleAccounts() again when the provider restarts, but
483864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // if we want to do additional actions we may need to use a service (e.g. start
483964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // EmptyService in onAccountsUpdated() and stop it when we finish here).
484064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
484164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
484264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            removeStaleAccounts(mAccounts);
484364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
484464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
484564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
484664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    /**
484764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * Makes sure there are no entries for accounts that no longer exist.
484864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     */
484964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void removeStaleAccounts(Account[] accounts) {
4850ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4851ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
4852ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
4853ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4854ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4855ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
48569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
485746f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
48589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
48599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
48609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
48619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
48629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
48642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
48659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
48669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            for (String table : new String[]{Tables.CALENDARS, Tables.COLORS}) {
4868ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
48699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
48702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.rawQuery("SELECT DISTINCT " +
48712ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_NAME +
48727cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
48732ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_TYPE +
48747cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
48759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
48764cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // ACCOUNT_TYPE_LOCAL is to store calendars not associated
48774cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // with a system account. Typically, a calendar must be
48784cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // associated with an account on the device or it will be
48794cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // deleted.
4880b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    if (c.getString(0) != null
4881b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && c.getString(1) != null
4882b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && !TextUtils.equals(c.getString(1),
4883b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                    CalendarContract.ACCOUNT_TYPE_LOCAL)) {
48849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
48859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
48869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
48879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
48889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
48899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
48909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
48912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = null;
48929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
48939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
48949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
4895f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
4896f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
4897f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
48989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
4899b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
49002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // This will be a no-op for accounts without a color palette.
49012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                mDb.execSQL(SQL_DELETE_FROM_COLORS, params);
49029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
49039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
49049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
49059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
49062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
49072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
49082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
49099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
49109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
49113ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
49123ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
4913dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
49149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
49159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4916636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
4917636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
4918636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
4919636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
4920636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
4921636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
4922636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
4923636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
4924636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
4925636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
4926636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
4927636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
4928636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
4929636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
4930636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
4931636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
4932636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
4933636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
4934636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
49359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
4936