CalendarProvider2.java revision c832113820b3fe514077b45dc4daaae970ef3284
19f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/*
29f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
39f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Copyright 2006, The Android Open Source Project
49f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
59f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Licensed under the Apache License, Version 2.0 (the "License");
69f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** you may not use this file except in compliance with the License.
79f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** You may obtain a copy of the License at
89f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
99f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**     http://www.apache.org/licenses/LICENSE-2.0
109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Unless required by applicable law or agreed to in writing, software
129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** distributed under the License is distributed on an "AS IS" BASIS,
139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** See the License for the specific language governing permissions and
149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** limitations under the License.
169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff*/
179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpackage com.android.providers.calendar;
199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
207be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
217be45683e367bd6897daf6444b03be938f8f5eaaErikimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
22370f91c0cfe5a5fecaba6120e703f4d2271d2277Erikimport com.google.common.annotations.VisibleForTesting;
23370f91c0cfe5a5fecaba6120e703f4d2271d2277Erik
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
41a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
42a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
44f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglioimport android.pim.EventRecurrence;
459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.pim.RecurrenceSet;
469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
47b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
48b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Attendees;
49b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
50b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
51b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
52b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
53b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
54b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.SyncState;
559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
561edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
57192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
60ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
622ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErikimport java.lang.reflect.Array;
639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
64ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
67dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
68bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFaddenimport java.util.Set;
699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
70dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
7181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
75b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik * is defined in {@link android.provider.CalendarContract}.
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
808bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
827be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
83c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String ACCOUNT_SELECTION_PREFIX = Calendars.ACCOUNT_NAME + "=? AND "
84c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            + Calendars.ACCOUNT_TYPE + "=?";
857be45683e367bd6897daf6444b03be938f8f5eaaErik
86f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final boolean PROFILE = false;
879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
888f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
891ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
96c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            Events.ORIGINAL_SYNC_ID,
979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
989ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
1027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_ORIGINAL_EVENT_INDEX = 3;
1037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees._ID,
1067e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            Attendees.EVENT_ID, // Assume these are the same for each table
1077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
112646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
113646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
114646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
115646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
116646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
117646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
118646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
119646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
120646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
121646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
122646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
123646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
124646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
125646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
126646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
127646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
133ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
136f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    private CalendarInstancesHelper mInstancesHelper;
1379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1388ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // The extended property name for storing an Event original Timezone.
139f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // Due to an issue in Calendar Server restricting the length of the name we
140f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    // had to strip it down
1418ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // TODO - Better name would be:
1428ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    // "com.android.providers.calendar.CalendarSyncAdapter#originalTimezone"
1438ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio    protected static final String EXT_PROP_ORIGINAL_TIMEZONE =
1448ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio        "CalendarSyncAdapter#originalTimezone";
1458ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
1463443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
147b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + ", " +
148b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTSTART_2445 + ", " +
149b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTEND_2445 + ", " +
1503443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1513443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
152b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
153b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1543443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
155b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
156b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
157b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_UPDATE_EVENT_SET_DIRTY = "UPDATE " +
158b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
159c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            " SET " + Events.DIRTY + "=1" +
160b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
161b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
1622ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    protected static final String SQL_WHERE_ID = Events._ID + "=?";
163b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EVENT_ID = "event_id=?";
164ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak    private static final String SQL_WHERE_ORIGINAL_EVENT = Events.ORIGINAL_SYNC_ID + "=?";
165b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
166b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " +
167b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID;
168b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
169b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
170b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
171b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID;
172b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
173b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
1742ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
175b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
176b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
177b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
1782ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
179b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
180b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
181b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
182b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
183b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
184b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            Tables.EXTENDED_PROPERTIES + "." + CalendarContract.ExtendedProperties._ID + "=?";
185b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
186b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
1872ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                " WHERE " + Calendars.ACCOUNT_NAME + "=? AND " +
1882ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    Calendars.ACCOUNT_TYPE + "=?";
189b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
190fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
191fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
192fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
1939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
1949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
1959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
1969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
1979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
1999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
20281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
20381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
20481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
20581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
206b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
20781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
208b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
20981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
21018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
21118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
21218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
21318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
21418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
215b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
21618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
217b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")" + ") LEFT OUTER JOIN " +
21818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
21918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
220b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Attendees.EVENT_ID + "=" +
22118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
222b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
22318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
224b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
225b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.START_DAY + "<=? AND " +
226b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END_DAY + ">=?";
22781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
228b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
229b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.BEGIN + "<=? AND " +
230b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END + ">=?";
2319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
2392ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * The sort order is: events with an earlier start time occur first and if
2402ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the start times are the same, then events with a later end time occur
2412ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * first. The later end time is ordered first so that long-running events in
2422ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the calendar views appear first. If the start and end times of two events
2432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * are the same then we sort alphabetically on the title. This isn't
2442ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * required for correctness, it just adds a nice touch.
2452ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
2462ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
2472ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
2482ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
2492ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * A regex for describing how we split search queries into tokens. Keeps
2502ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * quoted phrases as one token. "one \"two three\"" ==> ["one" "two three"]
251dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
252dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
253dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
254dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
255dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
256dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
257dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
258dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
259dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
260dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
26181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
262dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
263dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
264dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
265dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
266dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
267dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
268dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
269dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
270dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
27181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
27218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
27318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
27418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
27518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
27618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
277b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_EMAIL + ")";
27818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
27918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
28018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
28118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
28218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
28318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
284b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_NAME + ")";
28518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
28681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
287b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.TITLE,
288b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.DESCRIPTION,
289b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.EVENT_LOCATION,
29018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
29118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
29281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
29381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
294a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
295a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
296a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
297a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
298a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
299a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
300a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
301a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
302a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
303a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
304a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
305a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
306dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
307dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
308dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
309dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
310dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
311bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Set of columns allowed to be altered when creating an exception to a recurring event. */
312bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final HashSet<String> ALLOWED_IN_EXCEPTION = new HashSet<String>();
313bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    static {
314bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // _id, _sync_account, _sync_account_type, dirty, _sync_mark, calendar_id
315bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events._SYNC_ID);
316bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA1);
317bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA7);
31802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA3);
319bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.TITLE);
320bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_LOCATION);
321bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DESCRIPTION);
322bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.STATUS);
323c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SELF_ATTENDEE_STATUS);
32402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA6);
325bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DTSTART);
326c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // dtend -- set from duration as part of creating the exception
327bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_TIMEZONE);
328bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_END_TIMEZONE);
329bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DURATION);
330bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ALL_DAY);
331bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ACCESS_LEVEL);
332bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.AVAILABILITY);
333bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ALARM);
334bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_EXTENDED_PROPERTIES);
335bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RRULE);
336bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RDATE);
337bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXRULE);
338bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXDATE);
339bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_SYNC_ID);
340bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_INSTANCE_TIME);
341bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // originalAllDay, lastDate
342bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ATTENDEE_DATA);
343bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_MODIFY);
344bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_INVITE_OTHERS);
345bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_SEE_GUESTS);
346bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORGANIZER);
347bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // deleted, original_id, alerts
348bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
349bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
350bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Don't clone these from the base event into the exception event. */
351bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final String[] DONT_CLONE_INTO_EXCEPTION = {
352bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events._SYNC_ID,
353bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA1,
35402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA2,
35502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA3,
35602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA4,
35702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA5,
35802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA6,
359bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA7,
36002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA8,
361c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA9,
362c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA10,
363bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    };
364bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
365bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** set to 'true' to enable debug logging for recurrence exception code */
366bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final boolean DEBUG_EXCEPTION = false;
367bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
368dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
369e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
370e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
3718bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
3728bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
373420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
374420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected CalendarAlarmManager mCalendarAlarm;
375a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
376a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
377a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
378a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
379dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
380a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
381a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
382a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
383dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
384dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
385dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
386dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
387dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
388a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
389a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
390a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
391a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
3929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
3949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
3959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
3979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
3989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
3999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
405420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
409420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
411420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
4129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void verifyAccounts() {
4179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
4189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        onAccountsUpdated(AccountManager.get(getContext()).getAccounts());
4199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4278bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4288bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4298bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4308bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
431e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
432e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
433e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
434e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
435e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
436e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
437e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
4388bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4398bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
4429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
443ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
444ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
445ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
446f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
447f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
448f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
449ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
450ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
451ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
4529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
453ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
4548bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
4558bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
456dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
457e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
458e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
459ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
460ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
4619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4624caf8d015918f619a67d321a152f150a01022717Andy McFadden        mMetaData = new MetaData(mDbHelper);
4634caf8d015918f619a67d321a152f150a01022717Andy McFadden        mInstancesHelper = new CalendarInstancesHelper(mDbHelper, mMetaData);
4644caf8d015918f619a67d321a152f150a01022717Andy McFadden
4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
476e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
478ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
479ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
480420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // This is pulled out for testing
481420b7fb569773ae573fbe90c3a9c522d4c368863Erik        initCalendarAlarm();
482e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
483e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
4848bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
4859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
4869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
488420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected void initCalendarAlarm() {
489420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm = getOrCreateCalendarAlarmManager();
490420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.getScheduleNextAlarmWakeLock();
491e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
492e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
493e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
494420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mCalendarAlarm == null) {
495420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm = new CalendarAlarmManager(mContext);
496e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
497420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mCalendarAlarm;
498e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
499e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
500ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
501ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
502ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
503ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
504ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
505ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
506ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
507ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
508ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
509ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
510ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
511ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
512ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
513ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
514ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
515ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
5169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
519315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
5209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
5219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
5229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
5239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
5249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
5279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
5289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
5299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
530ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
5319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
5329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
535315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
536315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
537315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
538315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
539315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
540315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
541315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
542315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
5439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
5449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
5459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
546315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
547ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
548315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
549315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
550a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
551315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
552315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
553315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
554ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
555315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
556315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
557ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
558315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
559ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
560ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
561ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
562ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
563420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.rescheduleMissedAlarms();
564ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
565ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
566f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
567f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
568f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
569ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
570ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
571ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
572ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
573ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
574f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
575f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
576f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
577ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
5789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
579ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
580ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
581315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
582ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
583ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
5843443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
585ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
586315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
587ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
588ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
589ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
590ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
591ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
592ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
593ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
5943443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
5953443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
596ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
597ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
598ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
599ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
600ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6013443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
602f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
603f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
604f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
605f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
606f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
607f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
608f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
609ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6103443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
611ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
612ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
613ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
614ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
615ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
616ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
617ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
618ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
619ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
620ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
621ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
622f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
623f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
624f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
625ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
626ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
627ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
628ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
629ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
630ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
631f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
632f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
633f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
634ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
635ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
636ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
637ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
638ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
639ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
640ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
641ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
642ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
643b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
644b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
645ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
646b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
647dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
648ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
649ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
650ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
651ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
652ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
653ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
654ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
655ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
656ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
657ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
658ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
659f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
660f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
661f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
662ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
663ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
6649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
665ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
666ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
667ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
668ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
669315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
670315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
671ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
672ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
673ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
674ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
675ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
67625e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
677ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
678315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
679315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
680ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
681ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
682f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
683f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
684f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
685ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
686ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
687ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
688315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
689315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String type = mCalendarCache.readTimezoneType();
690315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return type.equals(CalendarCache.TIMEZONE_TYPE_HOME);
691315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
692315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
693ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
6949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
6959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
6969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
6979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
698315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
699315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7051f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7081f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7091f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7101f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7111f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7121f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7131f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
7142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* selection */, null,
7152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* sort */,
716d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
717315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
7182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    instancesTimezone, isHomeTimezone());
7191f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
7201f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
7211f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
7221f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
7231f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
7249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
725420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.rescheduleMissedAlarms();
7269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
730b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
7319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
7329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
733b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        mContentResolver.notifyChange(CalendarContract.CONTENT_URI, null, syncToNetwork);
7349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
7359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
7379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
7389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
739ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
740ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
7419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
7429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
7469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
7479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
748315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
7499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
7519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
7529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
7539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().query(db, projection, selection,  selectionArgs,
7549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
7559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
7569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
7571ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
7599ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
7609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
7619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
7631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
7649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
765636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
766b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
7679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
76819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
76919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
77019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
77119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
7729ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
7739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
77419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
77519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
77619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
77719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
778636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
779b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
78019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
78119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
78343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
784b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
7859ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendAccountFromParameterToSelection(selection, uri);
7869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
78843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
789b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
790636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
791b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
7929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
7939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
7949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
7959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
7969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
7979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
7989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
7999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
8019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
8059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
809315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
8102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceQuery(qb, begin, end, projection, selection, selectionArgs,
8112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        sortOrder, match == INSTANCES_BY_DAY, false /* don't force an expansion */,
812315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
81381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
81481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
81581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
81681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
81781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
81881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
81981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
82081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
82181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
82281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
82381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
82481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
82581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
82681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
827315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
82881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
82981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
8302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceSearchQuery(qb, begin, end, query, projection, selection,
8312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        selectionArgs, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
832315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8336db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
8349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
8359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
8369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
8389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
8409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
8419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
8429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
8439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
8459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
8469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
8479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
848315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
849315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
850315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
8519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
85202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
854b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(Tables.EVENTS + "." + Events._ID + "="
855b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        + Tables.ATTENDEES + "." + Attendees.EVENT_ID);
8569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
85802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
860636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
861b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
8629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
864b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
8659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
86702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
8689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
869636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
870b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
873b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
875b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
8769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
878b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
8799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
880b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
8819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
8829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
884b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
8859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
886636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
887b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
8889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
890b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
8919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
893b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
894636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
895b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
8969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
897315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
898b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
899315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
900315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
9019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
9029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
9039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
9069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
9109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
9119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
912ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
91339c65e5716e21e863d8de587d139dae85f99422fFred Quintana        if (projection != null && projection.length == 1
91439c65e5716e21e863d8de587d139dae85f99422fFred Quintana                && BaseColumns._COUNT.equals(projection[0])) {
91539c65e5716e21e863d8de587d139dae85f99422fFred Quintana            qb.setProjectionMap(sCountProjectionMap);
91639c65e5716e21e863d8de587d139dae85f99422fFred Quintana        }
91739c65e5716e21e863d8de587d139dae85f99422fFred Quintana
918ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
919ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
920ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
921ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
922ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
923ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
924ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
925ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
930b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            c.setNotificationUri(mContentResolver, CalendarContract.Events.CONTENT_URI);
9319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
9339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
9359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
9369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
9379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
9389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
9399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
9409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
9419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
9429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
9439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
9449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
9459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
946d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
947315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
948315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
9499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
9509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
9519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
9522ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            long rangeEnd, String[] projection, String selection, String[] selectionArgs,
9532ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String sort, boolean searchByDay, boolean forceExpansion,
9542ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
95681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
961315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
9629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
9639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
9669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
967315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
968315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
969b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
9709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
9719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
972315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
973315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
974b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
9759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
9762ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
9772ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = new String[] {String.valueOf(rangeEnd),
9788335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
9792ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
9802ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
9812ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
9822ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
9832ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
9842ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
9852ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
9868335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
9877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
9889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
9899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
99081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
9912ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * Combine a set of arrays in the order they are passed in. All arrays must
9922ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * be of the same type.
9932ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
9942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static <T> T[] combine(T[]... arrays) {
9952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (arrays.length == 0) {
9962ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            throw new IllegalArgumentException("Must supply at least 1 array to combine");
9972ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
9982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
9992ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int totalSize = 0;
10002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            totalSize += array.length;
10022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        T[] finalArray = (T[]) (Array.newInstance(arrays[0].getClass().getComponentType(),
10052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                totalSize));
10062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int currentPos = 0;
10082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
10092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            int length = array.length;
10102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            System.arraycopy(array, 0, finalArray, currentPos, length);
10112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            currentPos += array.length;
10122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
10132ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        return finalArray;
10142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    }
10152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
10162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
1017dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1018dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1019dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1020dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1021dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1022dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1023dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1024dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1025dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1026dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1027dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
102881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1029dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1030dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
103181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
103281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
103381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
103481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
103581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
103681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1037dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1038dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1039dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1040dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1041dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1042dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1043dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1044dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1045dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1046dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1047dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1048dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1049dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1050dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
105181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
105281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
105381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
105481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
105581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
105681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
105781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
105881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
105981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
106081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
106181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
106281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
106381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
106481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
106581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
106681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
106781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
106881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
106981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
107081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * approximation of full-text search across multiple columns.
107181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
107281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
107381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
107481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
107581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
107681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
107781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
107881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
107981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
108081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
108181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
108281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1083dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1084dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1085dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
108681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
108781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
108881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
108981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
109018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
109118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
109218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
109318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
109481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
109581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
109681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
109781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
109881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
109981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
110018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
110118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numArgs = tokens.length * numCols + 2;
110281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        // the additional two elements here are for begin/end time
110318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
110418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[0] =  String.valueOf(rangeEnd);
110518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        selectionArgs[1] =  String.valueOf(rangeBegin);
110681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1107f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            int start = 2 + numCols * j;
1108f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
110918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
111081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
111181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
111281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
111381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
111481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
111581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
111681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
11172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String selection, String[] selectionArgs, String sort, boolean searchByDay,
11182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
111918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
112081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
112181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1122dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
11232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
11242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
11252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
11262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
11272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // The appendWhere pieces get added first, so put the
11282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            // newSelectionArgs first.
11292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
11302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
113118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
113218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1133dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1134dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
113581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
113681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
113781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1138315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
113981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
114081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
114181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
114281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
114381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
114418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
114518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
114656292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
114756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1148315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1149315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1150315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
115156292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1152b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
115381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
115481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
115518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
115618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
115756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
115856292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1159315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1160315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1161315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
116256292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1163b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
116481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
116581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
116618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
116718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                Instances._ID /* groupBy */, searchWhere /* having */, sort);
116881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
116981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
11706db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1171315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1172315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
117381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
11746db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
117543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
117643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1177315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1178192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
117943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
118043556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1181192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
118243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1183315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1184315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1185b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
11868335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
11878335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
11888335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
11896db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
11909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
11929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
11939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
11949ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * table.  Acquires the database lock and calls
11959ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #acquireInstanceRangeLocked(long, long, boolean, boolean, String, boolean)}.
11969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
11979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
11989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
11999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1200d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1201315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1202315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1204d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1205315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1206315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
12079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
12089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1209315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1210315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
12119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
12129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
12139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
12149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
12169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
12189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
12199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
12209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
12219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
12229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
12239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1224315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1225315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1226315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
12279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1228420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1229315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
12309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
12319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
12329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1233315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1234315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1235315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1236315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1237315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
12389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
12399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
12409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
12419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
12429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
12439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
12449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
12459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
12469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
12479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
12489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
12519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
12529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
12539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
12549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
12559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1256315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1257315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1258315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1259315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1260315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1261315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
12637be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
12647be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
12657be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
12667be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1267315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1268315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1269315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1270d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
12719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1272b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1273f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
12746db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
12759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
12769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1277f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1278315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1279315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
12809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1281315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
12827be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
12837be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
12847be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
12857be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
12867be45683e367bd6897daf6444b03be938f8f5eaaErik
12877be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
1288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (timezoneType.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
12897be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
12907be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
12917be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
12927be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1293315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
12949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
12959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
12969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
12979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
12989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
12999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
13009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
13019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
13029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
13039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
13049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
13059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
13069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
13079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
13089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1309f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
13109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
13119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
13129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
13149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
13179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
13189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1319f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
13249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
13259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1326f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1331315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
13329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
13359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
13369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
13399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
13409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
13546db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
13559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
135648587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
135748587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1358315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1359315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1365fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalEvent) {
1366fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (!TextUtils.isEmpty(rrule)||
1367fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                !TextUtils.isEmpty(rdate)||
1368fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                !TextUtils.isEmpty(originalEvent));
13699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1371646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1372646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1373646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1374646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1375646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * corrects the fields DTSTART, DTEND, and DURATION if necessary. Also checks to ensure that
1376646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * either both DTSTART and DTEND or DTSTART and DURATION are set for each event.
1377646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1378646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @param updatedValues The values to check and correct
1379646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1380646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1381646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private boolean fixAllDayTime(Uri uri, ContentValues updatedValues) {
1382646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1383646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        if (updatedValues.containsKey(Events.ALL_DAY)
1384646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                && updatedValues.getAsInteger(Events.ALL_DAY).intValue() == 1) {
1385646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtstart = updatedValues.getAsLong(Events.DTSTART);
1386646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Long dtend = updatedValues.getAsLong(Events.DTEND);
1387646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String duration = updatedValues.getAsString(Events.DURATION);
1388646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Time time = new Time();
1389646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            Cursor currentTimesCursor = null;
1390646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            String tempValue;
1391646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If a complete set of time fields doesn't exist query the db for them. A complete set
1392646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // is dtstart and dtend for non-recurring events or dtstart and duration for recurring
1393646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // events.
1394646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if(dtstart == null || (dtend == null && duration == null)) {
1395646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we have an id to search for, if not this is probably a new event
1396646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 2) {
1397646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    currentTimesCursor = query(uri,
1398646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            ALLDAY_TIME_PROJECTION,
1399646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selection */,
1400646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* selectionArgs */,
1401646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            null /* sort */);
1402646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    if (currentTimesCursor != null) {
1403646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (!currentTimesCursor.moveToFirst() ||
1404646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                                currentTimesCursor.getCount() != 1) {
1405646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // Either this is a new event or the query is too general to get data
1406646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // from the db. In either case don't try to use the query and catch
1407646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            // errors when trying to update the time fields.
1408646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor.close();
1409646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            currentTimesCursor = null;
1410646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
1411646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1412646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1413646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1414646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1415646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // Ensure dtstart exists for this event (always required) and set so h,m,s are 0 if
1416646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // necessary.
1417646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // TODO Move this somewhere to check all events, not just allDay events.
1418646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtstart == null) {
1419646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (currentTimesCursor != null) {
1420646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // getLong returns 0 for empty fields, we'd like to know if a field is empty
1421646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    // so getString is used instead.
1422646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    tempValue = currentTimesCursor.getString(ALLDAY_DTSTART_INDEX);
1423646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    try {
1424646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        dtstart = Long.valueOf(tempValue);
1425646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    } catch (NumberFormatException e) {
1426646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        currentTimesCursor.close();
1427646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        throw new IllegalArgumentException("Event has no DTSTART field, the db " +
1428646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                            "may be damaged. Set DTSTART for this event to fix.");
1429646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    }
1430646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
1431646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    throw new IllegalArgumentException("DTSTART cannot be empty for new events.");
1432646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1433646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1434646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1435646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.set(dtstart.longValue());
1436646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1437646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1438646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1439646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1440646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                updatedValues.put(Events.DTSTART, time.toMillis(true));
1441646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1442646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1443646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1444646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            // If dtend exists for this event make sure it's h,m,s are 0.
1445646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend == null && currentTimesCursor != null) {
1446646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // getLong returns 0 for empty fields. We'd like to know if a field is empty
1447646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // so getString is used instead.
1448646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                tempValue = currentTimesCursor.getString(ALLDAY_DTEND_INDEX);
1449646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                try {
1450646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = Long.valueOf(tempValue);
1451646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } catch (NumberFormatException e) {
1452646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = null;
1453646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1454646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1455646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (dtend != null) {
1456646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.clear(Time.TIMEZONE_UTC);
1457646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.set(dtend.longValue());
1458646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1459646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.hour = 0;
1460646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.minute = 0;
1461646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    time.second = 0;
1462646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    dtend = time.toMillis(true);
1463646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DTEND, dtend);
1464646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1465646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1466646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1467646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1468646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (currentTimesCursor != null) {
1469646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (duration == null) {
1470646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = currentTimesCursor.getString(ALLDAY_DURATION_INDEX);
1471646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1472646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                currentTimesCursor.close();
1473646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1474646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1475646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration != null) {
1476646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                int len = duration.length();
1477646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1478646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 * in the seconds format, and if so converts it to days.
1479646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                 */
1480646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (len == 0) {
1481646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = null;
1482646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else if (duration.charAt(0) == 'P' &&
1483646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        duration.charAt(len - 1) == 'S') {
1484646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int seconds = Integer.parseInt(duration.substring(1, len - 1));
1485646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1486646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    duration = "P" + days + "D";
1487646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    updatedValues.put(Events.DURATION, duration);
1488646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    neededCorrection = true;
1489646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1490646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1491646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1492646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (duration == null && dtend == null) {
1493646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
1494646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        "an event.");
1495646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1496646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1497646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1498646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1499646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1500bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1501bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1502bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Determines whether the strings in the set name columns that may be overridden
1503bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * when creating a recurring event exception.
1504bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1505bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This uses a white list because it screens out unknown columns and is a bit safer to
1506bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * maintain than a black list.
1507bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1508bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private void checkAllowedInException(Set<String> keys) {
1509bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        for (String str : keys) {
1510bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!ALLOWED_IN_EXCEPTION.contains(str.intern())) {
1511bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                throw new IllegalArgumentException("Exceptions can't overwrite " + str);
1512bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1513bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1514bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1515bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1516bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1517bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Prepares an update to a recurrent event so it will stop just before a new series
1518bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * begins.  This is useful when modifying "this and all future events".
1519bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1520bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * The "until" time is specified in the rrule in UTC.  If the original event was an "all day"
1521bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * event, we also need to convert the dtstart to UTC.
1522bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1523bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param values Event description; must include EVENT_TIMEZONE and DTSTART.
1524bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param endTimeMillis The time before which the event must end (i.e. the start time of the
1525bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *        exception event instance).
1526bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return Values to apply to the existing event.
1527bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1528bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static ContentValues setRecurrenceEnd(ContentValues values, long endTimeMillis) {
1529bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1530bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        String oldRrule = values.getAsString(Events.RRULE);
1531bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1532bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        EventRecurrence eventRecurrence = new EventRecurrence();
1533bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        eventRecurrence.parse(oldRrule);
1534bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1535bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time untilTime = new Time();
1536bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time dtstart = new Time();
1537bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1538bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // The "until" time must be in UTC time in order for Google calendar
1539bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // to display it properly. For all-day events, the "until" time string
1540bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // must include just the date field, and not the time field. The
1541bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // repeating events repeat up to and including the "until" time.
1542bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        untilTime.timezone = Time.TIMEZONE_UTC;
1543bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.timezone = values.getAsString(Events.EVENT_TIMEZONE);
1544bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.set(values.getAsLong(Events.DTSTART));
1545bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1546bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Subtract one second from the exception begin time to get the "until" time.
1547bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis)
1548bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (allDay) {
1549bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            untilTime.hour = untilTime.minute = untilTime.second = 0;
1550bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            untilTime.allDay = true;
1551bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            untilTime.normalize(false);
1552bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1553bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            dtstart.hour = dtstart.minute = dtstart.second = 0;
1554bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            dtstart.allDay = true;
1555bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            dtstart.timezone = Time.TIMEZONE_UTC;
1556bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1557bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        eventRecurrence.until = untilTime.format2445();
1558bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1559bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ContentValues updateValues = new ContentValues();
1560bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.RRULE, eventRecurrence.toString());
1561bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.DTSTART, dtstart.normalize(true));
1562bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        return updateValues;
1563bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1564bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1565bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1566bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Handles insertion of an exception to a recurring event.
1567bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1568bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * There are two modes, selected based on the presence of "rrule" in modValues:
1569bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <ol>
1570bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Create a single instance exception ("modify current event only").
1571bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Cap the original event, and create a new recurring event ("modify this and all
1572bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * future events").
1573bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * </ol>
1574bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This may be used for "modify all instances of the event" by simply selecting the
1575bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * very first instance as the exception target.  In that case, the ID of the "new"
1576bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * exception event will be the same as the originalEventId.
1577bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1578bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param originalEventId The _id of the event to be modified
1579bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param modValues Event columns to update
1580c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden     * @param callerIsSyncAdapter Set if the content provider client is the sync adapter
1581bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return the ID of the new "exception" event, or -1 on failure
1582bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1583c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden    private long handleInsertException(long originalEventId, ContentValues modValues,
1584c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            boolean callerIsSyncAdapter) {
1585bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (DEBUG_EXCEPTION) {
1586bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            Log.i(TAG, "RE: values: " + modValues.toString());
1587bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1588bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1589bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Make sure they have specified an instance via originalInstanceTime.
1590bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Long originalInstanceTime = modValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1591bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (originalInstanceTime == null) {
1592bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            throw new IllegalArgumentException("Exceptions must specify " +
1593bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Events.ORIGINAL_INSTANCE_TIME);
1594bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1595bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1596bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Check for attempts to override values that shouldn't be touched.
1597bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        checkAllowedInException(modValues.keySet());
1598bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1599c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // If this isn't the sync adapter, set the "dirty" flag in any Event we modify.
1600c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        if (!callerIsSyncAdapter) {
1601c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            modValues.put(Events.DIRTY, true);
1602c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        }
1603c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1604bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Wrap all database accesses in a transaction.
1605bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        mDb.beginTransaction();
1606bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Cursor cursor = null;
1607bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        try {
1608bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: verify that there's an instance corresponding to the specified time
1609bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (does this matter? it's weird, but not fatal?)
1610bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1611bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Grab the full set of columns for this event.
1612bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor = mDb.query(Tables.EVENTS, null /* columns */,
1613bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    SQL_WHERE_ID, new String[] { String.valueOf(originalEventId) },
1614bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    null /* groupBy */, null /* having */, null /* sortOrder */);
1615bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor.getCount() != 1) {
1616bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event ID " + originalEventId + " lookup failed (count is " +
1617bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        cursor.getCount() + ")");
1618bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1619bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1620bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //DatabaseUtils.dumpCursor(cursor);
1621bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1622bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1623bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * Verify that the original event is in fact a recurring event by checking for the
1624bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * presence of an RRULE.  If it's there, we assume that the event is otherwise
1625bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * properly constructed (e.g. no DTEND).
1626bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1627bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor.moveToFirst();
1628bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int rruleCol = cursor.getColumnIndex(Events.RRULE);
1629bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (TextUtils.isEmpty(cursor.getString(rruleCol))) {
1630bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event has no rrule");
1631bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1632bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1633bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (DEBUG_EXCEPTION) {
1634bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.d(TAG, "RE: old RRULE is " + cursor.getString(rruleCol));
1635bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1636bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1637bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Verify that the original event is not itself a (single-instance) exception.
1638bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int originalIdCol = cursor.getColumnIndex(Events.ORIGINAL_ID);
1639bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!TextUtils.isEmpty(cursor.getString(originalIdCol))) {
1640bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event is an exception");
1641bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1642bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1643bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1644bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createSingleException = TextUtils.isEmpty(modValues.getAsString(Events.RRULE));
1645bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1646bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: check for the presence of an existing exception on this event+instance?
1647bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       The caller should be modifying that, not creating another exception.
1648bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (Alternatively, we could do that for them.)
1649bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1650bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Create a new ContentValues for the new event.  Start with the original event,
1651bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // and drop in the new caller-supplied values.  This will set originalInstanceTime.
1652bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            ContentValues values = new ContentValues();
1653bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
1654bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1655bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createNewEvent = true;
1656bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createSingleException) {
1657bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1658bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Save a copy of a few fields that will migrate to new places.
1659bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1660bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _id = values.getAsString(Events._ID);
1661bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _sync_id = values.getAsString(Events._SYNC_ID);
1662bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1663bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1664bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1665bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Wipe out some fields that we don't want to clone into the exception event.
1666bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1667bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                for (String str : DONT_CLONE_INTO_EXCEPTION) {
1668bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(str);
1669bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1670bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1671bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1672bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Merge the new values on top of the existing values.  Note this sets
1673bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * originalInstanceTime.
1674bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1675bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.putAll(modValues);
1676bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1677bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1678bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Copy some fields to their "original" counterparts:
1679bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _id --> original_id
1680bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _sync_id --> original_sync_id
1681bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   allDay --> originalAllDay
1682bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1683bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this event hasn't been sync'ed with the server yet, the _sync_id field will
1684bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * be null.  We will need to fill original_sync_id in later.  (May not be able to
1685bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * do it right when our own _sync_id field gets populated, because the order of
1686bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * events from the server may not be what we want -- could update the exception
1687bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * before updating the original event.)
1688bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1689bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * _id is removed later (right before we write the event).
1690bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1691bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ID, _id);
1692bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_SYNC_ID, _sync_id);
1693bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ALL_DAY, allDay);
1694bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1695bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // Mark the exception event status as "tentative", unless the caller has some
1696bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // other value in mind (like STATUS_CANCELED).
1697bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (!values.containsKey(Events.STATUS)) {
1698bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
1699bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1700bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1701bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // We're converting from recurring to non-recurring.  Clear out RRULE and replace
1702bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // DURATION with DTEND.
1703c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                values.remove(Events.RRULE);
1704bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1705bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Duration duration = new Duration();
1706bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String durationStr = values.getAsString(Events.DURATION);
1707bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                try {
1708bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    duration.parse(durationStr);
1709bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } catch (Exception ex) {
1710bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // NullPointerException if the original event had no duration.
1711bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // DateException if the duration was malformed.
1712bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Bad duration in recurring event: " + durationStr, ex);
1713bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1714bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1715bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1716c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1717c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * We want to compute DTEND as an offset from the start time of the instance.
1718c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If the caller specified a new value for DTSTART, we want to use that; if not,
1719c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the DTSTART in "values" will be the start time of the first instance in the
1720c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * recurrence, so we want to replace it with ORIGINAL_INSTANCE_TIME.
1721c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1722c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                long start;
1723c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.DTSTART)) {
1724c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.DTSTART);
1725c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                } else {
1726c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1727c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    values.put(Events.DTSTART, start);
1728c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1729bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.DTEND, start + duration.getMillis());
1730bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1731c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    Log.d(TAG, "RE: ORIG_INST_TIME=" + start +
1732c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            ", duration=" + duration.getMillis() +
1733bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            ", generated DTEND=" + values.getAsLong(Events.DTEND));
1734bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1735bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1736bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1737bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * We're going to "split" the recurring event, making the old one stop before
1738bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * this instance, and creating a new recurring event that starts here.
1739bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1740bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * No need to fill out the "original" fields -- the new event is not tied to
1741bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * the previous event in any way.
1742bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1743bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this is the first event in the series, we can just update the existing
1744bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * event with the values.
1745bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1746bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean canceling = (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
1747bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1748bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (originalInstanceTime.equals(values.getAsLong(Events.DTSTART))) {
1749bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1750bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Update fields in the existing event.  Rather than use the merged data
1751bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * from the cursor, we just do the update with the new value set after
1752bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * removing the ORIGINAL_INSTANCE_TIME entry.
1753bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1754bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (canceling) {
1755bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        // TODO: should we just call deleteEventInternal?
1756bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "Note: canceling entire event via exception call");
1757bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1758bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1759bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: updating full event");
1760bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1761bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    modValues.remove(Events.ORIGINAL_INSTANCE_TIME);
1762bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
1763bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1764bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    createNewEvent = false; // skip event creation and related-table cloning
1765bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } else {
1766bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1767bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: splitting event");
1768bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1769bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1770bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1771bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Cap the original event so it ends just before the target instance.
1772bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1773bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    ContentValues splitValues = setRecurrenceEnd(values, originalInstanceTime);
1774bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, splitValues, SQL_WHERE_ID,
1775bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1776bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1777bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1778bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Create the new event.  We remove originalInstanceTime, because we're now
1779bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * creating a new event rather than an exception.
1780bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     *
1781bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * We're always cloning a non-exception event (we tested to make sure the
1782bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * event doesn't specify original_id, and we don't allow original_id in the
1783bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * modValues), so we shouldn't end up creating a new event that looks like
1784bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * an exception.
1785bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1786bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.putAll(modValues);
1787bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(Events.ORIGINAL_INSTANCE_TIME);
1788bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1789c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            }
1790bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1791bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            long newEventId;
1792bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createNewEvent) {
1793bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.remove(Events._ID);      // don't try to set this explicitly
1794bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                validateEventData(values);
1795bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1796bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = mDb.insert(Tables.EVENTS, null, values);
1797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (newEventId < 0) {
1798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Unable to add exception to recurring event");
1799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Values: " + values);
1800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1803bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.d(TAG, "RE: new ID is " + newEventId);
1804bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1805bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1806bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1807bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Some of the other tables (Attendees, Reminders, ExtendedProperties) reference
1808c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the Event ID.  We need to copy the entries from the old event, filling in the
1809c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * new event ID, so that somebody doing a SELECT on those tables will find
1810c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * matching entries.
1811bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1812bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                CalendarDatabaseHelper.copyEventRelatedTables(mDb, newEventId, originalEventId);
1813c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1814c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1815c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If we modified Event.selfAttendeeStatus, we need to keep the corresponding
1816c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * entry in the Attendees table in sync.
1817c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1818c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
1819c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    /*
1820c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * Each Attendee is identified by email address.  To find the entry that
1821c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * corresponds to "self", we want to compare that address to the owner of
1822c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * the Calendar.  We're expecting to find one matching entry in Attendees.
1823c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     */
1824c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    long calendarId = values.getAsLong(Events.CALENDAR_ID);
1825c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    cursor = mDb.query(Tables.CALENDARS, new String[] { Calendars.OWNER_ACCOUNT },
1826c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            SQL_WHERE_ID, new String[] { String.valueOf(calendarId) },
1827c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            null /* groupBy */, null /* having */, null /* sortOrder */);
1828c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    if (!cursor.moveToFirst()) {
1829c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        Log.w(TAG, "Can't get calendar account_name for calendar " + calendarId);
1830c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    } else {
1831c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        String accountName = cursor.getString(0);
1832c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        ContentValues attValues = new ContentValues();
1833c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        attValues.put(Attendees.ATTENDEE_STATUS,
1834c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                modValues.getAsString(Events.SELF_ATTENDEE_STATUS));
1835c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1836c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (DEBUG_EXCEPTION) {
1837c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.d(TAG, "Updating attendee status for event=" + newEventId +
1838c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    " name=" + accountName + " to " +
1839c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    attValues.getAsString(Attendees.ATTENDEE_STATUS));
1840c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1841c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        int count = mDb.update(Tables.ATTENDEES, attValues,
1842c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
1843c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                new String[] { String.valueOf(newEventId), accountName });
1844c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (count != 1) {
1845c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.wtf(TAG, "Attendee status update touched " + count + " rows");
1846c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
1847c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    }
1848c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    cursor.close();
1849c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1850bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1851bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = originalEventId;
1852bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1853bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1854bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1855bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * The database keeps track of the range of dates for which the Instances table has
1856bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * been populated.  To ensure that the Instances are re-generated, we zero out
1857bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * these values.
1858bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * (TODO: this causes re-eval of instances for all events; is there a
1859bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * way to invalidate instances for a single event?)
1860bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1861bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mMetaData.clearInstanceRange();
1862bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1863bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.setTransactionSuccessful();
1864bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            return newEventId;
1865bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        } finally {
1866bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor != null) {
1867bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                cursor.close();
1868bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1869bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.endTransaction();
1870bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1871bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1872bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
18739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
1874b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
1875ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
18769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
18779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
18780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
18790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
18800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                null /* selection */, null /* selection args */);
18819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
18839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
18849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
1885bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case SYNCSTATE:
18869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
18879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
18889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
18897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
1890c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
18917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
18929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
18939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("DTSTART field missing from event");
18949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
18959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
1896e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
1897e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                validateEventData(updatedValues);
1898e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
1899e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
19009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
19019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
19029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
19039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
19049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
19059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues.containsKey(Events.CALENDAR_ID) &&
19069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        !updatedValues.containsKey(Events.ORGANIZER)) {
19079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
19089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
19099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
19109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
19119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
19129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
19139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
19149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
19159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
191634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
191734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
191834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    long originalId = getOriginalId(updatedValues
191934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsString(Events.ORIGINAL_SYNC_ID));
192034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (originalId != -1) {
192134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_ID, originalId);
192234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
192334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
192434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && updatedValues.containsKey(Events.ORIGINAL_ID)) {
192534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    String originalSyncId = getOriginalSyncId(updatedValues
192634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsLong(Events.ORIGINAL_ID));
192734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (!TextUtils.isEmpty(originalSyncId)) {
192834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
192934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
193034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                }
1931646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(uri, updatedValues)) {
1932f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
1933f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
1934f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
1935f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
1936646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
1937c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
19389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
19399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
19409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
1941f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                    mInstancesHelper.updateInstancesLocked(updatedValues, id,
1942f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                            true /* new event */, mDb);
19439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
19449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
19459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
19469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
19479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
19489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
19499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            owner = getOwner(updatedValues.getAsLong(Events.CALENDAR_ID));
19509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
19519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
19529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
19538ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // if the Event Timezone is defined, store it as the original one in the
19548ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    // ExtendedProperties table
19558ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    if (values.containsKey(Events.EVENT_TIMEZONE) && !callerIsSyncAdapter) {
19568ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        String originalTimezone = values.getAsString(Events.EVENT_TIMEZONE);
19578ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
19588ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        ContentValues expropsValues = new ContentValues();
1959b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.EVENT_ID, id);
1960b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.NAME,
19618ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                EXT_PROP_ORIGINAL_TIMEZONE);
1962b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                        expropsValues.put(CalendarContract.ExtendedProperties.VALUE,
1963b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                originalTimezone);
19648ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio
19658ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        // Insert the extended property
19668ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        long exPropId = mDbHelper.extendedPropertiesInsert(expropsValues);
19678ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        if (exPropId == -1) {
19688ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.ERROR)) {
19698ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                Log.e(TAG, "Cannot add the original Timezone in the "
19708ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                        + "ExtendedProperties table for Event: " + id);
19718ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
19728ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        } else {
19738ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            // Update the Event for saying it has some extended properties
19748ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            ContentValues eventValues = new ContentValues();
19758ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            eventValues.put(Events.HAS_EXTENDED_PROPERTIES, "1");
1976b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            int result = mDb.update("Events", eventValues, SQL_WHERE_ID,
19778ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    new String[] {String.valueOf(id)});
19788ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            if (result <= 0) {
19798ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                if (Log.isLoggable(TAG, Log.ERROR)) {
19808ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                    Log.e(TAG, "Cannot update hasExtendedProperties column"
19818ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                            + " for Event: " + id);
19828ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                                }
19838ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                            }
19848ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                        }
19858ba0d0238b153d331d612078b19492cb44728101Fabrice Di Meglio                    }
198634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    // If this was a recurring event with a _sync_id update any
198734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    // exceptions that may have been added prior to the original
198834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    // event
198934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (values.containsKey(Events._SYNC_ID) && values.containsKey(Events.RRULE)
199034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            && !TextUtils.isEmpty(values.getAsString(Events.RRULE))) {
199134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        String syncId = values.getAsString(Events._SYNC_ID);
199234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        if (TextUtils.isEmpty(syncId)) {
199334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            break;
199434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        }
199534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        ContentValues originalValues = new ContentValues();
199634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        originalValues.put(Events.ORIGINAL_ID, id);
199734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        mDb.update(Tables.EVENTS, originalValues, Events.ORIGINAL_SYNC_ID + "=?",
199834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                                new String[] {syncId});
199934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
2000dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
20019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2003bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID:
2004bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long originalEventId = ContentUris.parseId(uri);
2005c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                id = handleInsertException(originalEventId, values, callerIsSyncAdapter);
2006bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                break;
20079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
20089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
20099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
2010c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
20119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
2012c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                            Calendars.ACCOUNT_TYPE);
20139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
2014fa332ecedc0c340109811552407142f6e4f600b2RoboErik                    String eventsUrl = values.getAsString(Calendars.CAL_SYNC1);
20151b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
20169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2018dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
20199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
20219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
20229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
20239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20269ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Attendees.EVENT_ID);
20279ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
20289ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
20297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20309ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.attendeesInsert(values);
20319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
20339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
20349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
20369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Reminders.EVENT_ID)) {
20379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
20389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20407e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
20419ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Reminders.EVENT_ID);
20429ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
20439ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
20447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20459ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.remindersInsert(values);
20469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
20489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
20499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
20509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2051420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
20529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
20549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(CalendarAlerts.EVENT_ID)) {
20559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
20569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
20592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
20602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
20619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
2063b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                if (!values.containsKey(CalendarContract.ExtendedProperties.EVENT_ID)) {
20649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
20659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
20669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
20677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2068b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    final Long eventId = values
2069b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            .getAsLong(CalendarContract.ExtendedProperties.EVENT_ID);
20709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
20719ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
20727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
20739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.extendedPropertiesInsert(values);
20749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
20759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
20769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
20779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
20789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
20799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
20809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
20816db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2082315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
20837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
20849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
20859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
20869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
20899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
20909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
20919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
20929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
20939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
20949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2095e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
209695229b60087d6c25f80bbcdebf47ac110c01df68RoboErik     * Do some validation on event data before inserting. In particular make
209795229b60087d6c25f80bbcdebf47ac110c01df68RoboErik     * sure calendar_id exists and dtend, duration, etc make sense for the type
209895229b60087d6c25f80bbcdebf47ac110c01df68RoboErik     * of event (regular, recurrence, exception). Remove any unexpected fields.
2099e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
2100e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues to insert
2101e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2102e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private void validateEventData(ContentValues values) {
2103e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2104e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2105e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2106e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
210795229b60087d6c25f80bbcdebf47ac110c01df68RoboErik        boolean hasCalId = !TextUtils.isEmpty(values.getAsString(Events.CALENDAR_ID));
2108c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_SYNC_ID));
2109e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
211095229b60087d6c25f80bbcdebf47ac110c01df68RoboErik        if (!hasCalId) {
211195229b60087d6c25f80bbcdebf47ac110c01df68RoboErik            throw new IllegalArgumentException("New events must include a calendar_id.");
211295229b60087d6c25f80bbcdebf47ac110c01df68RoboErik        }
2113e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2114e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2115e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2116e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2117e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2118e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is the recurrence rule
2119e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2120e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2121e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2122e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
2123e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2124e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence: " + values);
2125e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2126e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2127c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                values.remove(Events.ORIGINAL_SYNC_ID);
2128e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
2129e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2130e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2131e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2132e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2133e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2134e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2135e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2136e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2137e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2138e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2139e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
2140e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2141e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for recurrence exception: " + values);
2142e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2143e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2144e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2145e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2146e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2147e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2148e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2149e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2150e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2151e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2152e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2153e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2154e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
2155e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
2156e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                    Log.e(TAG, "Invalid values for event: " + values);
2157e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2158e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
2159e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2160e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2161e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2162e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
21639ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private void setEventDirty(long eventId) {
21649ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY, new Object[] {eventId});
21657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
21667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
216734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private long getOriginalId(String originalSyncId) {
216834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (TextUtils.isEmpty(originalSyncId)) {
216934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return -1;
217034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
217134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
217234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        long originalId = -1;
217334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
217434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
217534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
217634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._SYNC_ID + "=?", new String[] {originalSyncId}, null);
217734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
217834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalId = c.getLong(0);
217934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
218034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
218134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
218234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
218334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
218434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
218534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalId;
218634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
218734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
218834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private String getOriginalSyncId(long originalId) {
218934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (originalId == -1) {
219034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return null;
219134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
219234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
219334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        String originalSyncId = null;
219434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
219534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
219634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
219734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
219834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
219934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalSyncId = c.getString(0);
220034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
220134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
220234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
220334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
220434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
220534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
220634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalSyncId;
220734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
220834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
22099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Gets the calendar's owner for an event.
22119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param calId
22129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2215f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2216f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2217f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2218f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2219f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2220f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
22219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
22229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
22239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
22249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
22259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
22269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
22279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
22299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
22309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2231f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2232f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2233f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
22349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
22379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
22389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
22399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
22409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
22419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
22429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
22439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
22479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
22489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
22509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
22519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
22529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
22539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
22559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
22569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
22579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
22589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
22599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
22609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
22619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
22629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
22639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
22649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
22669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
22679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
22689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
22699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
22719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
22729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
22739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
22749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
22759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param attendeeValues the column values for one row in the Attendees
22769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.
22779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
22789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
22799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
22809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long eventId = attendeeValues.getAsLong(Attendees.EVENT_ID);
22819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
22839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
22849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
22859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
22869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
22879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
22889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2293f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2294f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2295f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
22969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
22979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
22999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
23019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
23029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
23089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
23099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
23109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
23119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
23139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2315f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2316f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2317f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
23189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
23199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
23219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
23229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
23239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
23249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
23289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
23339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
23359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
23389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
23409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
23419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
23429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_RELATIONSHIP)) {
23479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            int rel = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
23489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
23509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (attendeeValues.containsKey(Attendees.ATTENDEE_STATUS)) {
23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            status = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
23559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
23589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2359b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2360b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
23649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
23659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
23669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
23679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
23689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
23709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
23719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
23729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
23739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
23749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
23829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
23839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
23949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
23959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
23969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2397f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2398f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2399f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2400f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2401f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2402f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2403b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            values.get(CalendarContract.Events.RRULE), e);
2404f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2405f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2406f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
24079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2408f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
24099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
24109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
24119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
24139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
24159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
24169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
24179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
24189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
24199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
24219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
24239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
24249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
24259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
24269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
24279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
24289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
24299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
24309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
24339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
24349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
24369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2438e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2439e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
2440e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @param values the ContentValues (in/out)
2441e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2442e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2443e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
24449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
24459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
24469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
24479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
24489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
24499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
24519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
24529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
2453f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2454f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
2455f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
24569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
24579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
24599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
24619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
24629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2463b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        rawValues.put(CalendarContract.EventsRawTimes.EVENT_ID, eventId);
24649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
24669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
24689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
24699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
24709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
24719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
24749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
24759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
24769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
24799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
24809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
24819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
24829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
2483b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTSTART_2445, time.format2445());
24849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
24879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
24889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
2489b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTEND_2445, time.format2445());
24909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
24919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
24929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
24939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
24949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
24959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
24969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
24979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
24989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
24999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
25009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
2502b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445,
2503b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
25049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
25079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
25089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
25099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
2510b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.LAST_DATE_2445, time.format2445());
25119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
25149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
25159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2517b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
2518b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
2519ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
25209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
25219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
25229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
25230739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
25240739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
25250739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
25269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
25279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
25289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
25299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
25312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
25329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
25339323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2534dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2535dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2536dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
2537dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
25389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
25391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
25409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25417e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
25420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection = appendSyncAccountToSelection(uri, selection);
25437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
25441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
2545ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak                Cursor cursor = mDb.query(Views.EVENTS, ID_ONLY_PROJECTION,
25461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
25477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
25489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
25491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
25501ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
255110b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
25529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2553420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
2554dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
25559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
25569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
25579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
25589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
25599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
25609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25611ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
25621ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
25631ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
25641ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                if (selection != null) {
25651ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    throw new UnsupportedOperationException("CalendarProvider2 "
25661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + "doesn't support selection based deletion for type "
25671ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                            + match);
25681ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
256910b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
25701ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
2571bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID2:
2572bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            {
2573bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // This will throw NumberFormatException on missing or malformed input.
2574bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                List<String> segments = uri.getPathSegments();
2575bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long eventId = Long.parseLong(segments.get(1));
2576bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long excepId = Long.parseLong(segments.get(2));
2577bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // TODO: verify that this is an exception instance (has an ORIGINAL_ID field
2578bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //       that matches the supplied eventId)
2579bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return deleteEventInternal(excepId, callerIsSyncAdapter, false /* isBatch */);
2580bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
25819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
25829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2584b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
25857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2586b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, selection, selectionArgs);
25877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
25889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
25899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
25909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
25912fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
25922fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
25932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
25947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
25957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2596b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
2597b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
25987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2599b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.ATTENDEES, uri, null /* selection */,
26002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
26017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
26029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
26049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
26057e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2606b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, selection, selectionArgs);
26077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2608b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, selection, selectionArgs);
26097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
26109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
26129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
26132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
26142fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
26152fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
26167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
26177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
2618b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.REMINDERS, SQL_WHERE_ID,
2619b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
26207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2621b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.REMINDERS, uri, null /* selection */,
26222fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
26232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
26242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
26252fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
26262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
26272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
2628b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
26292fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2630b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, selection,
2631b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
26322fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
26332fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
26342fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
26352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
26362fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
26372fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
26382fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
26392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
26402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
2641b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
2642636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
26432fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
2644b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.EXTENDED_PROPERTIES, uri, null /* selection */,
26452fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
26467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
26479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
26499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
26507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
2651b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
26527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
2653b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return deleteFromTable(Tables.CALENDAR_ALERTS, uri, selection, selectionArgs);
26547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
26559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
26579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
26582fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
26592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
26602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
26612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
26622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
26639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
2664b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
2665b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
26669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
26679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
26682ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                StringBuilder selectionSb = new StringBuilder(Calendars._ID + "=");
26699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
26709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
26719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
26729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
26739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
26749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
26759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
26769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // fall through to CALENDARS for the actual delete
26779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
2678595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff                selection = appendAccountToSelection(uri, selection);
267974ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                return deleteMatchingCalendars(selection, selectionArgs);
26809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
26819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
26826db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2683315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
26849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
26859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
26869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
26879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
26889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
26899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
269010b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
26911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
2692192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
26931ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
26941ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
2695b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
2696b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
2697636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
26981ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
26991ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
27001ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
27011ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
27021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
270348f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
27041ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
27051ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
27061ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
27071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
27081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
27091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
27101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String origEvent = cursor.getString(EVENTS_ORIGINAL_EVENT_INDEX);
2711fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isRecurrenceEvent(rrule, rdate, origEvent)) {
27121ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
27131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
27141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
271548f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
271648f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
2717bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //
2718bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // The EVENTS_CLEANUP_TRIGGER_SQL trigger will remove all associated data
2719bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // (Attendees, Instances, Reminders, etc).
272048f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
2721b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
27221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
2723bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // Event is on the server, so we "soft delete", i.e. mark as deleted so that
2724bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the sync adapter has a chance to tell the server about the deletion.  After
2725bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the server sees the change, the sync adapter will do the "hard delete"
2726bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // (above).
27271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
27281b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
2729c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
2730b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
273102494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
273243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
273343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
2734b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
2735b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
2736b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
2737b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
2738b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
2739b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
27401ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
27411ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
27421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
27431ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
27441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
27451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
27468f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
274710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
2748420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
2749dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
275010b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
27511ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
27521ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
27531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
27547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
27557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Delete rows from a table and mark corresponding events as dirty.
27567e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
27577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
27587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
27597e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
27607e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
27617e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int deleteFromTable(String table, Uri uri, String selection, String[] selectionArgs) {
27627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
27637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
27649ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
27659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final ContentValues values = new ContentValues();
2766c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        values.put(Events.DIRTY, "1");
27677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
27687e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
27697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
27709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long id = c.getLong(ID_INDEX);
27719ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long event_id = c.getLong(EVENT_ID_INDEX);
27729ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDbHelper.duplicateEvent(event_id);
27739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDb.delete(table, SQL_WHERE_ID, new String[]{String.valueOf(id)});
2774b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
2775b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
27767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
27777e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
27787e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
27797e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
27807e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
27817e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
27827e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
27837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
27847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
27857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * Update rows in a table and mark corresponding events as dirty.
27867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
27877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param values The values to update
27887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
27897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
27907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
27917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
27927e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private int updateInTable(String table, ContentValues values, Uri uri, String selection,
27937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            String[] selectionArgs) {
27947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // Note that the query will return data according to the access restrictions,
27957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        // so we don't need to worry about deleting data we don't have permission to read.
27969ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, null);
27979ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final ContentValues dirtyValues = new ContentValues();
2798c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        dirtyValues.put(Events.DIRTY, "1");
27997e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
28007e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
28017e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            while(c.moveToNext()) {
28029ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long id = c.getLong(ID_INDEX);
28039ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                final long event_id = c.getLong(EVENT_ID_INDEX);
28049ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDbHelper.duplicateEvent(event_id);
2805b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(table, values, SQL_WHERE_ID, new String[] {String.valueOf(id)});
2806b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
2807b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(event_id)});
28087e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
28097e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
28107e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
28117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
28127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
28137e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
28147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
28157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
281674ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio    private int deleteMatchingCalendars(String selection, String[] selectionArgs) {
28179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
28189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
28199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
282074ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, selection,
282174ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                selectionArgs,
282274ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* groupBy */,
282374ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* having */,
282474ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* sortOrder */);
28259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
28269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
28299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
28309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
283674ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        return mDb.delete(Tables.CALENDARS, selection, selectionArgs);
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2839fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private Cursor getCursorForEventIdAndProjection(String eventId, String[] projection) {
2840fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return mDb.query(Tables.EVENTS,
2841fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                projection,
2842fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                SQL_WHERE_ID,
2843fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { eventId },
2844fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* group by */,
2845fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* having */,
2846fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                null /* order by*/);
2847fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
2848fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2849fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
2850fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
2851fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
2852fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
2853fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
2854fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
2855fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
2856fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
2857fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
2858fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
2859fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
2860fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2861fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
2862fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
2863fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
2864fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
2865fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
2866fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
2867fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
2868fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
2869fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
2870fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
2871fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesStatusCancelUpdateMeanUpdate(String eventId, ContentValues values) {
2872fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        boolean isStatusCanceled = values.containsKey(Events.STATUS) &&
2873fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
2874fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
2875fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            Cursor cursor = null;
2876fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            try {
2877fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                cursor = getCursorForEventIdAndProjection(eventId,
2878c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                        new String[] { Events.RRULE, Events.RDATE, Events.ORIGINAL_SYNC_ID });
2879fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (!cursor.moveToFirst()) {
2880fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2881fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        Log.w(TAG, "Cannot find Event with id: " + eventId);
2882fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
2883fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    return false;
2884fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
2885fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String rrule = cursor.getString(0);
2886fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String rdate = cursor.getString(1);
2887fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String originalEvent = cursor.getString(2);
2888fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2889fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                boolean isRecurrenceException =
2890fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        isRecurrenceEvent(rrule, rdate, originalEvent) &&
2891fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        !TextUtils.isEmpty(originalEvent);
2892fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2893fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isRecurrenceException) {
2894fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    return doesEventExistForSyncId(originalEvent);
2895fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
2896fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            } finally {
2897fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                cursor.close();
2898fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
2899fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
2900fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        // This is the normal case, we just want an UPDATE
2901fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return true;
2902fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
2903fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
29049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // TODO: call calculateLastDate()!
29059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
29069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
2907b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
2908ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
29099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
29120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
29130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int count = 0;
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: remove this restriction
291843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio        if (!TextUtils.isEmpty(selection) && match != CALENDAR_ALERTS
2919315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                && match != EVENTS && match != CALENDARS && match != PROVIDER_PROPERTIES) {
2920b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            throw new IllegalArgumentException("WHERE based updates not supported");
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
2922fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        appendAccountToSelection(uri, selection), selectionArgs);
29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
29299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = appendAccountToSelection(uri, selection);
29302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
2931dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
29329323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
2933dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
2934dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
2935dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
293843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
29409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
294143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
294243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
294343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    if (selection != null) {
294443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        throw new UnsupportedOperationException("Selection not permitted for "
294543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                                + uri);
294643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
294743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
294843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
294943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
295043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
295143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
295243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
295343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
29544cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    if (selection != null && TextUtils.equals(selection,"_id=?")) {
29554cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                        id = Long.parseLong(selectionArgs[0]);
29564cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
295743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
295843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
295943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
296043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
2961b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
296243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
296343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
296443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
2965c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Calendars.DIRTY, 1);
29662fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
29679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2972b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
2973636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
29749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29753ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
2976d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
29774067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                    if (values.containsKey(Calendars.VISIBLE)) {
2978d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
2979d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
2980d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
2981d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
2982420b7fb569773ae573fbe90c3a9c522d4c368863Erik                        mCalendarAlarm.scheduleNextAlarm(false);
2983d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
29843ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
2985dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
29863ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
29873ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
29889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
29899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
29919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
29929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
29937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                long id = 0;
29947e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (match == EVENTS_ID) {
29957e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    id = ContentUris.parseId(uri);
2996a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                } else if (callerIsSyncAdapter) {
299743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: same remark as for CALENDARS/CALENDARS_ID case as this is not
299843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // sufficient to deal with all the "_id" case in selection
2999da641c374b00946f37bfe00e53bb292f4e0103d8RoboErik                    if (selection != null && selection.startsWith("_id=?")) {
3000da641c374b00946f37bfe00e53bb292f4e0103d8RoboErik                        id = Long.parseLong(selectionArgs[0]);
3001da641c374b00946f37bfe00e53bb292f4e0103d8RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
30027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // The ContentProviderOperation generates an _id=n string instead of
30037e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        // adding the id to the URL, so parse that out here.
30047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        id = Long.parseLong(selection.substring(4));
3005a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    } else {
3006a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // Sync adapter Events operation affects just Events table, not associated
3007a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        // tables.
3008646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        if (fixAllDayTime(uri, values)) {
3009f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            if (Log.isLoggable(TAG, Log.WARN)) {
3010f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                Log.w(TAG, "updateInTransaction: Caller is sync adapter. " +
3011f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                        "allDay is true but sec, min, hour were not 0.");
3012f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                            }
3013646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                        }
3014a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                        return mDb.update("Events", values, selection, selectionArgs);
3015a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    }
30167e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3017a7f687007ff4d0c30726bf86f717fde88f51b453Ken Shirriff                    throw new IllegalArgumentException("Unknown URL " + uri);
30187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
3020c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
30217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
30229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Disallow updating the attendee status in the Events
30239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // table.  In the future, we could support this but we
30249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // would have to query and update the attendees table
30259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // to keep the values consistent.
30269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
30279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Updating "
30289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + Events.SELF_ATTENDEE_STATUS
30299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + " in Events table is not allowed.");
30309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3031fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                String strId = String.valueOf(id);
3032fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                // For taking care about recurrences exceptions cancelations, check if this needs
3033fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                //  to be an UPDATE or a DELETE
3034fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                boolean isUpdate = doesStatusCancelUpdateMeanUpdate(strId, values);
3035e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
3036e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // TODO: should extend validateEventData to work with updates and call it here
3037e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
30389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
3039f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3040f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "Could not update event.");
3041f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
30429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return 0;
30439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3044646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                // Make sure we pass in a uri with the id appended to fixAllDayTime
3045646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                Uri allDayUri;
3046646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (uri.getPathSegments().size() == 1) {
3047646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = ContentUris.withAppendedId(uri, id);
3048646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                } else {
3049646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                    allDayUri = uri;
3050646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
3051646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                if (fixAllDayTime(allDayUri, updatedValues)) {
3052f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
3053f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "updateInTransaction: " +
3054f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
3055f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
3056646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3058fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                int result;
30599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3060fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                if (isUpdate) {
30619ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    // If a user made a change, possibly duplicate the event so we can do a partial
30629ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    // update. If a sync adapter made a change and that change marks an event as
30639ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    // un-dirty, remove any duplicates that may have been created earlier.
30649ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    if (!callerIsSyncAdapter) {
30659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                        mDbHelper.duplicateEvent(id);
30669ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    } else {
30679ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                        if (values.containsKey(Events.DIRTY)
30689ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                                && values.getAsInteger(Events.DIRTY) == 0) {
30699ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                            mDbHelper.removeDuplicateEvent(id);
30709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                        }
30719ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    }
3072fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    result = mDb.update(Tables.EVENTS, updatedValues, SQL_WHERE_ID,
3073fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            new String[] { strId });
3074fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    if (result > 0) {
3075fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        updateEventRawTimesLocked(id, updatedValues);
3076f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                        mInstancesHelper.updateInstancesLocked(updatedValues, id,
3077f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                                false /* not a new event */, mDb);
3078fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
307906c305d35741db303bd3aacd0eab5af8de0ab34eErik                        if (values.containsKey(Events.DTSTART) ||
308006c305d35741db303bd3aacd0eab5af8de0ab34eErik                                values.containsKey(Events.STATUS)) {
308106c305d35741db303bd3aacd0eab5af8de0ab34eErik                            // If this is a cancellation knock it out
308206c305d35741db303bd3aacd0eab5af8de0ab34eErik                            // of the instances table
308306c305d35741db303bd3aacd0eab5af8de0ab34eErik                            if (values.containsKey(Events.STATUS) &&
308406c305d35741db303bd3aacd0eab5af8de0ab34eErik                                    values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED) {
308506c305d35741db303bd3aacd0eab5af8de0ab34eErik                                String[] args = new String[] {String.valueOf(id)};
308606c305d35741db303bd3aacd0eab5af8de0ab34eErik                                mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, args);
308706c305d35741db303bd3aacd0eab5af8de0ab34eErik                            }
308806c305d35741db303bd3aacd0eab5af8de0ab34eErik
3089fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            // The start time of the event changed, so run the
3090fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            // event alarm scheduler.
3091fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            if (Log.isLoggable(TAG, Log.DEBUG)) {
3092fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                                Log.d(TAG, "updateInternal() changing event");
3093fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                            }
3094420b7fb569773ae573fbe90c3a9c522d4c368863Erik                            mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
30959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
30963ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
3097fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                        sendUpdateNotification(id, callerIsSyncAdapter);
3098fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3099fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                } else {
3100fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    result = deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3101420b7fb569773ae573fbe90c3a9c522d4c368863Erik                    mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
3102fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    sendUpdateNotification(callerIsSyncAdapter);
31039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3104fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
31059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
31069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31072fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case ATTENDEES_ID: {
31082fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31102fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
31129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
31139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
31147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3116b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.ATTENDEES, values, SQL_WHERE_ID,
311783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
31187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3119b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.ATTENDEES, values, uri, null /* selection */,
31202fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
31217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31232fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
31242fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31252fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31262fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31272fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
31282fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
31299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3130b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
3131636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
31329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31332fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
31342fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
31352fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
3136b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
31379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31382fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
31392fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31402fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31412fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3144b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = mDb.update(Tables.REMINDERS, values, SQL_WHERE_ID,
314583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                            new String[] {String.valueOf(id)});
31467e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3147b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    count = updateInTable(Tables.REMINDERS, values, uri, null /* selection */,
31482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                            null /* selectionArgs */);
31497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
31519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
31529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
31539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
31549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
31559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
3156420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */);
31577e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
31589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31592fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID: {
31602fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (selection != null) {
31612fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    throw new UnsupportedOperationException("Selection not permitted for " + uri);
31622fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31647e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3165b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.update(Tables.EXTENDED_PROPERTIES, values, SQL_WHERE_ID,
3166636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
31677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
3168b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return updateInTable(Tables.EXTENDED_PROPERTIES, values, uri,
3169b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            null /* selection */, null /* selectionArgs */);
31707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
317283512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // TODO: replace the SCHEDULE_ALARM private URIs with a
317383512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            // service
317483512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM: {
3175420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(false);
317683512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
317783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
317883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
3179420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.scheduleNextAlarm(true);
318083512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
318183512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
31829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3183315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
3184315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (selection == null) {
3185315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection cannot be null for " + uri);
3186315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3187315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
3188315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
3189315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3190315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3191315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
3192315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3193315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
3194315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
3195315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
3196315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3197315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3198315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
3199315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
3200315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3201315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
3202315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
3203b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
3204315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3205315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
3206f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "home", set the Instances
3207f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the previous
3208f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "auto", set the Instances
3209f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the current
3210f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // device one
3211f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone Instances is set AND if we are in "home"
3212f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone type, then save the timezone Instance into
3213f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // "previous" too
3214315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
3215315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
3216315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
3217315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
3218315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
3219315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
3220315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
3221315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
3222315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
3223315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
3224315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
3225315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3226315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
3227d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
3228315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
3229315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3230d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3231315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3232315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3233315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
3234315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
3235315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
3236315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
3237315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
3238315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
3239d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
3240315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
3241315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3242315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3243315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3244315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
3245315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
3246315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
3247315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
3248315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
3249315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
3250315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
3251315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
3252d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
3253315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
3254315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
3255315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
3256d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
3257315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
3258315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
3259315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
3260315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
3261315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
3262315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
3263315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
32649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
32659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
32669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
32689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
32699ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendAccountFromParameterToSelection(String selection, Uri uri) {
3270b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3271b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
3272b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3273b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
3274595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
32759ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            final StringBuilder sb = new StringBuilder();
32769ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(Calendars.ACCOUNT_NAME + "=")
32779ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountName))
32789ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" AND ")
32799ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(Calendars.ACCOUNT_TYPE)
32809ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(" = ")
32819ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    .append(DatabaseUtils.sqlEscapeString(accountType));
32829ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(sb, selection);
3283595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        } else {
32849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
32859ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
32869ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
32879ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
32889ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendLastSyncedColumnToSelection(String selection, Uri uri) {
32899ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (getIsCallerSyncAdapter(uri)) {
32909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
3291595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
32929ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final StringBuilder sb = new StringBuilder();
3293b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sb.append(CalendarContract.Events.LAST_SYNCED).append(" = 0");
32949ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return appendSelection(sb, selection);
3295595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
3296595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
32979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String appendAccountToSelection(Uri uri, String selection) {
3298b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3299b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
3300b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3301b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
33029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!TextUtils.isEmpty(accountName)) {
3303b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Calendars.ACCOUNT_NAME
3304b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + "=" + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3305b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Calendars.ACCOUNT_TYPE + "="
33060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountType));
33079ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
33080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        } else {
33090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return selection;
33100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
33110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
33120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
33130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private String appendSyncAccountToSelection(Uri uri, String selection) {
33140739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountName = QueryParameterUtils.getQueryParameter(uri,
3315b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
33160739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountType = QueryParameterUtils.getQueryParameter(uri,
3317b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
33180739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (!TextUtils.isEmpty(accountName)) {
3319b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            StringBuilder selectionSb = new StringBuilder(CalendarContract.Events.ACCOUNT_NAME + "="
33200739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    + DatabaseUtils.sqlEscapeString(accountName) + " AND "
3321b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    + CalendarContract.Events.ACCOUNT_TYPE + "="
33229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    + DatabaseUtils.sqlEscapeString(accountType));
33239ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return appendSelection(selectionSb, selection);
33249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
33259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
33269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
33279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
33289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
33299ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendSelection(StringBuilder sb, String selection) {
33309ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (!TextUtils.isEmpty(selection)) {
33319ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(" AND (");
33329ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(selection);
33339ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(')');
33349ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
33359ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return sb.toString();
33369ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
33379ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
33380739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    /**
33390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * Verifies that the operation is allowed and throws an exception if it
33400739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * isn't. This defines the limits of a sync adapter call vs an app call.
3341c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik     *
33420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param type The type of call, {@link #TRANSACTION_QUERY},
33430739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_INSERT}, {@link #TRANSACTION_UPDATE}, or
33440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_DELETE}
33450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param uri
33460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param values
33470739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param isSyncAdapter
33480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     */
33490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyTransactionAllowed(int type, Uri uri, ContentValues values,
33500739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            boolean isSyncAdapter, int uriMatch, String selection, String[] selectionArgs) {
33510739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (type) {
33520739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_QUERY:
33530739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
33540739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_INSERT:
33550739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
33560739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException(
33570739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                            "Inserting into instances not supported");
33580739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
3359c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
3360c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
33610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
33620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
33630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
33640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
33650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
33660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
33670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
33680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
33690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_UPDATE:
33700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
33710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Updating instances not supported");
33720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
3373c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
3374c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
33750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
33760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
33770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
33780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
33790739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
33800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
33810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
33820739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
33830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_DELETE:
33840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
33850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Deleting instances not supported");
33860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
33870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
33880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
33890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
33900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
33910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
33920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
33930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
33940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
33950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyHasAccount(Uri uri, String selection, String[] selectionArgs) {
3396c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String accountName = QueryParameterUtils.getQueryParameter(uri, Calendars.ACCOUNT_NAME);
33970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String accountType = QueryParameterUtils.getQueryParameter(uri,
3398c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Calendars.ACCOUNT_TYPE);
33990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
34000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (selection != null && selection.startsWith(ACCOUNT_SELECTION_PREFIX)) {
34010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountName = selectionArgs[0];
34020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountType = selectionArgs[1];
34030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
34040739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
34050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
34060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            throw new IllegalArgumentException(
34070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    "Sync adapters must specify an account and account type: " + uri);
34080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
34090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
34100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
3411c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private void verifyColumns(ContentValues values, int uriMatch) {
3412c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
3413c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            return;
3414c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3415c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String[] columns;
3416c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        switch (uriMatch) {
3417c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
3418c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
3419c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
3420c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
3421c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = Events.PROVIDER_WRITABLE_COLUMNS;
3422c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3423c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            default:
3424c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = PROVIDER_WRITABLE_DEFAULT_COLUMNS;
3425c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3426c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3427c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
3428c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        for (int i = 0; i < columns.length; i++) {
3429c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            if (values.containsKey(columns[i])) {
3430c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                throw new IllegalArgumentException("Only the provider may write to " + columns[i]);
3431c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            }
3432c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
3433c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    }
3434c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
34350739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyNoSyncColumns(ContentValues values, int uriMatch) {
3436c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
34370739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return;
34380739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
34390739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String[] syncColumns;
34400739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (uriMatch) {
34410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS:
34420739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS_ID:
34430739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES:
34440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES_ID:
3445c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Calendars.SYNC_WRITABLE_COLUMNS;
3446c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
3447c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
3448c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
3449c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
3450c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
3451c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Events.SYNC_WRITABLE_COLUMNS;
34520739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
34530739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            default:
34540739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                syncColumns = SYNC_WRITABLE_DEFAULT_COLUMNS;
34550739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
34560739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
34570739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
34580739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        for (int i = 0; i < syncColumns.length; i++) {
34590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (values.containsKey(syncColumns[i])) {
34600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                throw new IllegalArgumentException("Only sync adapters may write to "
34610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                        + syncColumns[i]);
34620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
34630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
34640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
34650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
34669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
34679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
34689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
34699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
3470c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                new String[] {Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
3471fa332ecedc0c340109811552407142f6e4f600b2RoboErik                        Calendars.CAL_SYNC1, Calendars.SYNC_EVENTS},
34729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
34739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
34749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
34759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
34779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
34789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
3479ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
34809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
3481ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
3482ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
3483ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
3484ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
3485ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
3486ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
3487ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
34889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
34899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
34909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
34919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
34929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
34939535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
34949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
3495f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3496f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
3497f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
3498f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
34999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
35009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35029535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
35039535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
35049535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
35059535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
35069535627bf6295cd94447beb83e1aac41f50c3600Erik        }
35079535627bf6295cd94447beb83e1aac41f50c3600Erik
35089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
35099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
35109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
35119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
35129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
35139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
35149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
35159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
35169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
35179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3518a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3519a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3520a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3521a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
3522dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
35239ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
3524a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3525dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
3526dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
3527dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
3528a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3529a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3530a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3531a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
3532a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
3533a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
3534a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
3535a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
3536a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
3537a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
3538a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
35399ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param eventId the ID of the event that changed, or -1 for no specific event
35409ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
3541a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3542dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
3543dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
3544a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
3545a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
3546a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
3547a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
3548a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
3549dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
3550dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
3551dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
3552dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
3553dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
3554dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
3555dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
3556dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
3557dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
3558dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
3559dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
3560dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
3561dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
3562dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
3563dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
3564dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
3565dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
3566a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
3567dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
3568a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3569a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
3570a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
3571a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
3572a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
35739ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #sendUpdateNotification(boolean)} instead.
3574a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
35759ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @see #sendUpdateNotification(boolean)
3576a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
3577a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
3578a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
3579b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.CONTENT_URI);
3580f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
3581f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
3582f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
3583e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
3584a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
3585a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
35860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_QUERY = 0;
35870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_INSERT = 1;
35880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_UPDATE = 2;
35890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_DELETE = 3;
35900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
35910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:off
35920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final String[] SYNC_WRITABLE_DEFAULT_COLUMNS = new String[] {
3593b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars.DIRTY,
3594b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars._SYNC_ID
35950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    };
3596c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String[] PROVIDER_WRITABLE_DEFAULT_COLUMNS = new String[] {
3597c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    };
35980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:on
35990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
36009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
36019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
36029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
36032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS = 4;
36042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS_ID = 5;
36052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES = 6;
36062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES_ID = 7;
36072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS = 8;
36082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS_ID = 9;
36092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES = 10;
36102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES_ID = 11;
36112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS = 12;
36122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_ID = 13;
36132ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_BY_INSTANCE = 14;
36142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_BY_DAY = 15;
36152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE = 16;
36162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE_ID = 17;
36172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES = 18;
36182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES_ID = 19;
36192ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_DAYS = 20;
36202ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM = 21;
36212ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM_REMOVE = 22;
36222ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int TIME = 23;
36232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES = 24;
36242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES_ID = 25;
36252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH = 26;
36262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH_BY_DAY = 27;
36272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int PROVIDER_PROPERTIES = 28;
3628bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID = 29;
3629bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID2 = 30;
36309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
36329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
3633f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final HashMap<String, String> sEventsProjectionMap;
363419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
36359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
36369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
36379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
3638315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
363939c65e5716e21e863d8de587d139dae85f99422fFred Quintana    private static final HashMap<String, String> sCountProjectionMap;
36409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
36419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
3642b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/when/*/*", INSTANCES);
3643b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
3644b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
3645b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/searchbyday/*/*/*",
364681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
3647b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
3648b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events", EVENTS);
3649b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events/#", EVENTS_ID);
3650b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities", EVENT_ENTITIES);
3651b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
3652b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS);
3653b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS_ID);
3654b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
3655b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
3656b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees", ATTENDEES);
3657b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees/#", ATTENDEES_ID);
3658b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders", REMINDERS);
3659b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders/#", REMINDERS_ID);
3660b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
3661b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties/#",
3662b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                EXTENDED_PROPERTIES_ID);
3663b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
3664b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
3665b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/by_instance",
3666b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
3667b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate", SYNCSTATE);
3668b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
3669b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, CalendarAlarmManager.SCHEDULE_ALARM_PATH,
3670420b7fb569773ae573fbe90c3a9c522d4c368863Erik                SCHEDULE_ALARM);
3671b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY,
3672b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
3673b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time/#", TIME);
3674b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time", TIME);
3675b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "properties", PROVIDER_PROPERTIES);
3676b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#", EXCEPTION_ID);
3677b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#/#", EXCEPTION_ID2);
36789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
367939c65e5716e21e863d8de587d139dae85f99422fFred Quintana        /** Contains just BaseColumns._COUNT */
368039c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap = new HashMap<String, String>();
368139c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
368239c65e5716e21e863d8de587d139dae85f99422fFred Quintana
36839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
36849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
368502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_NAME, Events.ACCOUNT_NAME);
368602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_TYPE, Events.ACCOUNT_TYPE);
3687c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.TITLE, Events.TITLE);
3688c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
3689c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
3690c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.STATUS, Events.STATUS);
369102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
3692c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
3693c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTSTART, Events.DTSTART);
3694c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTEND, Events.DTEND);
3695c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
3696c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
3697c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DURATION, Events.DURATION);
3698c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
3699c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
3700c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
3701c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
3702c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, Events.HAS_EXTENDED_PROPERTIES);
3703c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RRULE, Events.RRULE);
3704c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RDATE, Events.RDATE);
3705c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
3706c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
3707c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
370834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
3709c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
3710c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
3711c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
3712c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
3713c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
3714c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, Events.GUESTS_CAN_INVITE_OTHERS);
3715c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
3716c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
3717c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
3718c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DELETED, Events.DELETED);
371902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
37209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3721e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
37221ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
37231ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
37241ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
37259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
3726c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
372702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CALENDAR_ACCESS_LEVEL);
3728c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
372902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
3730c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
373102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
373202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
373302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
373402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND, Calendars.CAN_ORGANIZER_RESPOND);
373502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
37369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3737982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
3738e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
3739e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
3740982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3741e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
3742982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
3743c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events._ID, Events._ID);
374402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
374502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
374602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
374702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
374802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
374902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
37509ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
375102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
375202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
375302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
375402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
375502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
375602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
375702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
375802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
375902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
376002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
376102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
376202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
376302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
3764c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DIRTY, Events.DIRTY);
37659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
37669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
376746f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
3768c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.TITLE, Events.TITLE);
3769c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
3770c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
3771c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.STATUS, Events.STATUS);
377202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
3773c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
3774c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTSTART, Events.DTSTART);
3775c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTEND, Events.DTEND);
3776c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
3777c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
3778c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DURATION, Events.DURATION);
3779c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
3780c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
3781c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
3782c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
3783c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES,
3784c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.HAS_EXTENDED_PROPERTIES);
3785c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RRULE, Events.RRULE);
3786c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RDATE, Events.RDATE);
3787c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
3788c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
3789c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
379034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
3791c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
3792c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.ORIGINAL_INSTANCE_TIME);
3793c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
3794c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
3795c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
3796c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
3797c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS,
3798c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.GUESTS_CAN_INVITE_OTHERS);
3799c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
3800c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
3801c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
3802c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DELETED, Events.DELETED);
380319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
380419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
380502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
380602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
380702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
380802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
380902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
381002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
38119ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
381202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
381302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
381402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
3815470aa5bc291ca33d51dda356f38ac2954026da9aAlon Albert        sEventEntitiesProjectionMap.put(Events.DIRTY, Events.DIRTY);
38169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
3817fa332ecedc0c340109811552407142f6e4f600b2RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
381802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
381902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
382002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
382102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
382202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
382302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
382402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
382502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
382602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
382719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
38289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
38291b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
38309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
38319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
38329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
38339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
38349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
38359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
38369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
38379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
38389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
38409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
38419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
38429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
38439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
38449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
38459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
38469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
384702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
384802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
38499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
38519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
38529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
38539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
38549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
38559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
38579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
38589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
38599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
38609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
38619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
38629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
38639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
3864315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
3865315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
3866315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
3867315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
3868315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
38699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
38709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
38729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make sure that there are no entries for accounts that no longer
38739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * exist. We are overriding this since we need to delete from the
38749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
38757e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
38767e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
38779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
3878f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    @Override
38799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
3880ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
3881ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            mDb = mDbHelper.getWritableDatabase();
3882ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
3883ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
3884ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
3885ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
38869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
388746f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashMap<Account, Boolean> accountHasCalendar = new HashMap<Account, Boolean>();
388846f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
38899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
38909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
38919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            accountHasCalendar.put(account, false);
38929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
38939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
38949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
38959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
38969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
38979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3898b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            for (String table : new String[]{Tables.CALENDARS}) {
3899ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
39009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
39017cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                Cursor c = mDb.rawQuery("SELECT DISTINCT " +
39022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_NAME +
39037cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
39042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_TYPE +
39057cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
39069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
39074cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // ACCOUNT_TYPE_LOCAL is to store calendars not associated
39084cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // with a system account. Typically, a calendar must be
39094cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // associated with an account on the device or it will be
39104cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // deleted.
3911b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    if (c.getString(0) != null
3912b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && c.getString(1) != null
3913b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && !TextUtils.equals(c.getString(1),
3914b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                    CalendarContract.ACCOUNT_TYPE_LOCAL)) {
39159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
39169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
39179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
39189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
39199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
39209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
39219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
39229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
3925f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
3926f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
3927f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
39289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
3929b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
39309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
39329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
39339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
39349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
39359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
39363ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
39373ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
3938dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
39399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
39409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3941636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
3942636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
3943636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
3944636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
3945636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
3946636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
3947636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
3948636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
3949636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
3950636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
3951636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
3952636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
3953636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
3954636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
3955636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
3956636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
3957636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
3958636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
3959636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
39609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
3961