19f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/*
29f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
39f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Copyright 2006, The Android Open Source Project
49f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
59f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Licensed under the Apache License, Version 2.0 (the "License");
69f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** you may not use this file except in compliance with the License.
79f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** You may obtain a copy of the License at
89f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
99f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**     http://www.apache.org/licenses/LICENSE-2.0
109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff**
119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** Unless required by applicable law or agreed to in writing, software
129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** distributed under the License is distributed on an "AS IS" BASIS,
139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** See the License for the specific language governing permissions and
149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff** limitations under the License.
169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff*/
179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpackage com.android.providers.calendar;
199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.Account;
219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.AccountManager;
229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.accounts.OnAccountsUpdateListener;
23043587d3ef1a9cc156a6819fdcb7ef5b2aa81ed4Dianne Hackbornimport android.app.AppOpsManager;
249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.BroadcastReceiver;
259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentResolver;
269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentUris;
279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.ContentValues;
289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Context;
299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.Intent;
309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.IntentFilter;
319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.content.UriMatcher;
327a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albertimport android.content.pm.PackageManager;
339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.Cursor;
349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.DatabaseUtils;
359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.SQLException;
369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteDatabase;
379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.database.sqlite.SQLiteQueryBuilder;
389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.net.Uri;
397a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albertimport android.os.Binder;
40a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Handler;
41a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tangimport android.os.Message;
429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.os.Process;
439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.provider.BaseColumns;
44b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract;
45b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Attendees;
46b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.CalendarAlerts;
47b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Calendars;
482f251c778c06d21ed7693a70f4a1268ff929242eRoboErikimport android.provider.CalendarContract.Colors;
49b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Events;
50b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Instances;
51b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.Reminders;
52b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErikimport android.provider.CalendarContract.SyncState;
539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.text.TextUtils;
541edaf77a7ef2fbad6b6116dd75591e0aeaff3a16Ken Shirriffimport android.text.format.DateUtils;
55192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blankimport android.text.format.Time;
569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.Log;
579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport android.util.TimeFormatException;
58ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglioimport android.util.TimeUtils;
599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
605cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.DateException;
615cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.Duration;
625cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.EventRecurrence;
635cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.RecurrenceProcessor;
645cd969369a0e025bad07ad32bda9c8c4f0630457Michael Chanimport com.android.calendarcommon2.RecurrenceSet;
658d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.providers.calendar.CalendarDatabaseHelper.Tables;
668d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.android.providers.calendar.CalendarDatabaseHelper.Views;
678d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.google.android.collect.Sets;
688d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albertimport com.google.common.annotations.VisibleForTesting;
698d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
703b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.io.File;
712ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErikimport java.lang.reflect.Array;
723b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFaddenimport java.lang.reflect.Method;
739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.ArrayList;
74ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglioimport java.util.Arrays;
759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashMap;
769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.HashSet;
771c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFaddenimport java.util.Iterator;
78dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.List;
79bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFaddenimport java.util.Set;
809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffimport java.util.TimeZone;
81dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tangimport java.util.regex.Matcher;
8281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tangimport java.util.regex.Pattern;
839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff/**
859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff * Calendar content provider. The contract between this provider and applications
86b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik * is defined in {@link android.provider.CalendarContract}.
879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff */
889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriffpublic class CalendarProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
918bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static final String TAG = "CalendarProvider2";
92350bdf916ba93f0318a5c3b0cbd2c9794c748c80Tony Mak    // Turn on for b/22449592
93350bdf916ba93f0318a5c3b0cbd2c9794c748c80Tony Mak    static final boolean DEBUG_INSTANCES = Log.isLoggable(TAG, Log.DEBUG);
949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
957be45683e367bd6897daf6444b03be938f8f5eaaErik    private static final String TIMEZONE_GMT = "GMT";
96c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String ACCOUNT_SELECTION_PREFIX = Calendars.ACCOUNT_NAME + "=? AND "
97c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            + Calendars.ACCOUNT_TYPE + "=?";
987be45683e367bd6897daf6444b03be938f8f5eaaErik
99f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final boolean PROFILE = false;
1009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
1018f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
1021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    private static final String[] ID_ONLY_PROJECTION =
1031ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            new String[] {Events._ID};
1049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] EVENTS_PROJECTION = new String[] {
1069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events._SYNC_ID,
1079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RRULE,
1089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Events.RDATE,
109b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            Events.ORIGINAL_ID,
110c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            Events.ORIGINAL_SYNC_ID,
1119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
1129ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
1139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_SYNC_ID_INDEX = 0;
1147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RRULE_INDEX = 1;
1157e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENTS_RDATE_INDEX = 2;
116b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_ID_INDEX = 3;
117b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    private static final int EVENTS_ORIGINAL_SYNC_ID_INDEX = 4;
1187e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
1192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] COLORS_PROJECTION = new String[] {
1202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_NAME,
1212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.ACCOUNT_TYPE,
1222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR_TYPE,
123387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        Colors.COLOR_KEY,
1242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Colors.COLOR,
1252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_NAME_INDEX = 0;
1272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_ACCOUNT_TYPE_INDEX = 1;
1282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_TYPE_INDEX = 2;
1292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX_INDEX = 3;
1302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS_COLOR_INDEX = 4;
1312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1324755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private static final String COLOR_FULL_SELECTION = Colors.ACCOUNT_NAME + "=? AND "
1334755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + Colors.ACCOUNT_TYPE + "=? AND " + Colors.COLOR_TYPE + "=? AND " + Colors.COLOR_KEY
1344755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            + "=?";
1354755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan
1362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_NAME = Calendars.ACCOUNT_NAME;
1372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String GENERIC_ACCOUNT_TYPE = Calendars.ACCOUNT_TYPE;
1382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String[] ACCOUNT_PROJECTION = new String[] {
1392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_NAME,
1402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        GENERIC_ACCOUNT_TYPE,
1412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    };
1422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_NAME_INDEX = 0;
1432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int ACCOUNT_TYPE_INDEX = 1;
1442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    // many tables have _id and event_id; pick a representative version to use as our generic
1461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_ID = Attendees._ID;
1471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private static final String GENERIC_EVENT_ID = Attendees.EVENT_ID;
1481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
1497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final String[] ID_PROJECTION = new String[] {
1501c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_ID,
1511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            GENERIC_EVENT_ID,
1527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    };
1537e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int ID_INDEX = 0;
1547e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    private static final int EVENT_ID_INDEX = 1;
1559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
157646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Projection to query for correcting times in allDay events.
158646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
159646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final String[] ALLDAY_TIME_PROJECTION = new String[] {
160646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events._ID,
161646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTSTART,
162646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DTEND,
163646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        Events.DURATION
164646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    };
165646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_ID_INDEX = 0;
166646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTSTART_INDEX = 1;
167646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DTEND_INDEX = 2;
168646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int ALLDAY_DURATION_INDEX = 3;
169646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
170646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
171646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
172646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * The cached copy of the CalendarMetaData database table.
1749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Make this "package private" instead of "private" so that test code
1759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * can access it.
1769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    MetaData mMetaData;
178ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    CalendarCache mCalendarCache;
1799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private CalendarDatabaseHelper mDbHelper;
181f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    private CalendarInstancesHelper mInstancesHelper;
1829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1833443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private static final String SQL_SELECT_EVENTSRAWTIMES = "SELECT " +
184b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + ", " +
185b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTSTART_2445 + ", " +
186b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.DTEND_2445 + ", " +
1873443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            Events.EVENT_TIMEZONE +
1883443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " FROM " +
189b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS_RAW_TIMES + ", " +
190b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.EVENTS +
1913443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            " WHERE " +
192b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            CalendarContract.EventsRawTimes.EVENT_ID + " = " + Tables.EVENTS + "." + Events._ID;
193b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
1947a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    private static final String SQL_UPDATE_EVENT_SET_DIRTY_AND_MUTATORS = "UPDATE " +
1957a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            Tables.EVENTS + " SET " +
1967a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            Events.DIRTY + "=1," +
1977a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            Events.MUTATORS + "=? " +
1987a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            " WHERE " + Events._ID + "=?";
1997a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert
2007a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    private static final String SQL_QUERY_EVENT_MUTATORS = "SELECT " + Events.MUTATORS +
2017a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            " FROM " + Tables.EVENTS +
202b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " WHERE " + Events._ID + "=?";
203b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_WHERE_CALENDAR_COLOR = Calendars.ACCOUNT_NAME + "=? AND "
205387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            + Calendars.ACCOUNT_TYPE + "=? AND " + Calendars.CALENDAR_COLOR_KEY + "=?";
2062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
207f4b1756f88c34efefb33b4103230ee334d9c9262Sara Ting    private static final String SQL_WHERE_EVENT_COLOR = "calendar_id in (SELECT _id from "
20810651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan            + Tables.CALENDARS + " WHERE " + Events.ACCOUNT_NAME + "=? AND " + Events.ACCOUNT_TYPE
20910651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan            + "=?) AND " + Events.EVENT_COLOR_KEY + "=?";
2102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
21124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    protected static final String SQL_WHERE_ID = GENERIC_ID + "=?";
21224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private static final String SQL_WHERE_EVENT_ID = GENERIC_EVENT_ID + "=?";
2134d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID = Events.ORIGINAL_ID + "=?";
2144d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden    private static final String SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID = Events.ORIGINAL_ID +
2154d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden            "=? AND " + Events._SYNC_ID + " IS NULL";
216ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
217ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan    private static final String SQL_WHERE_ATTENDEE_BASE =
218ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.ATTENDEES + "." + Attendees.EVENT_ID
219ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            + " AND " +
220ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
221ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan
222b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_ATTENDEES_ID =
223ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.ATTENDEES + "." + Attendees._ID + "=? AND " + SQL_WHERE_ATTENDEE_BASE;
224b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
225b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_REMINDERS_ID =
226b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.REMINDERS + "." + Reminders._ID + "=? AND " +
227ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events._ID + "=" + Tables.REMINDERS + "." + Reminders.EVENT_ID +
228ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            " AND " +
229ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan            Tables.EVENTS + "." + Events.CALENDAR_ID + "=" + Tables.CALENDARS + "." + Calendars._ID;
230b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
231b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT =
2322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
233b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID;
234b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
235b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_CALENDAR_ALERT_ID =
2362ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            Views.EVENTS + "." + Events._ID + "=" +
237b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    Tables.CALENDAR_ALERTS + "." + CalendarAlerts.EVENT_ID +
238b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            " AND " +
239b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            Tables.CALENDAR_ALERTS + "." + CalendarAlerts._ID + "=?";
240b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
241b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_EXTENDED_PROPERTIES_ID =
242b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            Tables.EXTENDED_PROPERTIES + "." + CalendarContract.ExtendedProperties._ID + "=?";
243b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
244b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_DELETE_FROM_CALENDARS = "DELETE FROM " + Tables.CALENDARS +
2452ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                " WHERE " + Calendars.ACCOUNT_NAME + "=? AND " +
2462ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    Calendars.ACCOUNT_TYPE + "=?";
247b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio
2482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final String SQL_DELETE_FROM_COLORS = "DELETE FROM " + Tables.COLORS + " WHERE "
2492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            + Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?";
2502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
251fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private static final String SQL_SELECT_COUNT_FOR_SYNC_ID =
252fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            "SELECT COUNT(*) FROM " + Tables.EVENTS + " WHERE " + Events._SYNC_ID + "=?";
253fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
2549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Make sure we load at least two months worth of data.
2559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    // Client apps can load more data in a background thread.
2569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final long MINIMUM_EXPANSION_SPAN =
2579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            2L * 31 * 24 * 60 * 60 * 1000;
2589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final String[] sCalendarsIdProjection = new String[] { Calendars._ID };
2609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int CALENDARS_INDEX_ID = 0;
2619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
26281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String INSTANCE_QUERY_TABLES =
26381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
26481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
26581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS +
26681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
267b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
26881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
269b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
27081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
27118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
27218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
27318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Views.EVENTS + " AS " +
27418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS +
27518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
276b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Instances.EVENT_ID + "=" +
27718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
278b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")" + ") LEFT OUTER JOIN " +
27918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.ATTENDEES +
28018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
281b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Attendees.EVENT_ID + "=" +
28218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        CalendarDatabaseHelper.Tables.EVENTS + "."
283b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        + CalendarContract.Events._ID + ")";
28418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
285b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN_DAY =
286b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.START_DAY + "<=? AND " +
287b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END_DAY + ">=?";
28881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
289b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio    private static final String SQL_WHERE_INSTANCES_BETWEEN =
290b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.BEGIN + "<=? AND " +
291b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Instances.END + ">=?";
2929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_DAY = 0;
2949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_DAY = 1;
2959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_START_MINUTE = 2;
2969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_END_MINUTE = 3;
2979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES_INDEX_ALL_DAY = 4;
2989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
3002ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * The sort order is: events with an earlier start time occur first and if
3012ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the start times are the same, then events with a later end time occur
3022ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * first. The later end time is ordered first so that long-running events in
3032ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * the calendar views appear first. If the start and end times of two events
3042ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * are the same then we sort alphabetically on the title. This isn't
3052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * required for correctness, it just adds a nice touch.
3062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
3072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
3082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
3092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
3102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * A regex for describing how we split search queries into tokens. Keeps
3112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * quoted phrases as one token. "one \"two three\"" ==> ["one" "two three"]
312dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
313dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_TOKEN_PATTERN =
314dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("[^\\s\"'.?!,]+|" // first part matches unquoted words
315dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                      + "\"([^\"]*)\"");  // second part matches quoted phrases
316dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
317dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A special character that was use to escape potentially problematic
318dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * characters in search queries.
319dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     *
320dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Note: do not use backslash for this, as it interferes with the regex
321dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * escaping mechanism.
32281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
323dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final String SEARCH_ESCAPE_CHAR = "#";
324dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
325dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
326dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * A regex for matching any characters in an incoming search query that we
327dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * need to escape with {@link #SEARCH_ESCAPE_CHAR}, including the escape
328dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * character itself.
329dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
330dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    private static final Pattern SEARCH_ESCAPE_PATTERN =
331dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
33281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
33318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
33418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee e-mails when grouping
33518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
33618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
33718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_EMAIL_CONCAT =
338b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_EMAIL + ")";
33918f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
34018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    /**
34118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * Alias used for aggregate concatenation of attendee names when grouping
34218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     * attendees by instance.
34318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang     */
34418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang    private static final String ATTENDEES_NAME_CONCAT =
345b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        "group_concat(" + CalendarContract.Attendees.ATTENDEE_NAME + ")";
34618f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang
34781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private static final String[] SEARCH_COLUMNS = new String[] {
348b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.TITLE,
349b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.DESCRIPTION,
350b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Events.EVENT_LOCATION,
35118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_EMAIL_CONCAT,
35218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        ATTENDEES_NAME_CONCAT
35381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    };
35481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
355a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
356a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Arbitrary integer that we assign to the messages that we send to this
357a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * thread's handler, indicating that these are requests to send an update
358a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * notification intent.
359a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
360a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final int UPDATE_BROADCAST_MSG = 1;
361a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
362a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
363a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Any requests to send a PROVIDER_CHANGED intent will be collapsed over
364a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * this window, to prevent spamming too many intents at once.
365a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
366a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private static final long UPDATE_BROADCAST_TIMEOUT_MILLIS =
367dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        DateUtils.SECOND_IN_MILLIS;
368dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
369dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private static final long SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS =
370dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        30 * DateUtils.SECOND_IN_MILLIS;
371dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang
3728d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private static final HashSet<String> ALLOWED_URI_PARAMETERS = Sets.newHashSet(
3738d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.CALLER_IS_SYNCADAPTER,
3748d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.EventsEntity.ACCOUNT_NAME,
3758d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            CalendarContract.EventsEntity.ACCOUNT_TYPE);
3768d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
377bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Set of columns allowed to be altered when creating an exception to a recurring event. */
378bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final HashSet<String> ALLOWED_IN_EXCEPTION = new HashSet<String>();
379bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    static {
380bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // _id, _sync_account, _sync_account_type, dirty, _sync_mark, calendar_id
381bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events._SYNC_ID);
382bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA1);
383bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA7);
38402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA3);
385bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.TITLE);
386bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_LOCATION);
387bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DESCRIPTION);
3882f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR);
389387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        ALLOWED_IN_EXCEPTION.add(Events.EVENT_COLOR_KEY);
390bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.STATUS);
391c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.SELF_ATTENDEE_STATUS);
39202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        ALLOWED_IN_EXCEPTION.add(Events.SYNC_DATA6);
393bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DTSTART);
394c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // dtend -- set from duration as part of creating the exception
395bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_TIMEZONE);
396bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EVENT_END_TIMEZONE);
397bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.DURATION);
398bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ALL_DAY);
399bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ACCESS_LEVEL);
400bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.AVAILABILITY);
401bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ALARM);
402bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_EXTENDED_PROPERTIES);
403bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RRULE);
404bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.RDATE);
405bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXRULE);
406bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.EXDATE);
407bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_SYNC_ID);
408bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORIGINAL_INSTANCE_TIME);
409bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // originalAllDay, lastDate
410bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.HAS_ATTENDEE_DATA);
411bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_MODIFY);
412bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_INVITE_OTHERS);
413bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.GUESTS_CAN_SEE_GUESTS);
414bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ALLOWED_IN_EXCEPTION.add(Events.ORGANIZER);
415c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        ALLOWED_IN_EXCEPTION.add(Events.CUSTOM_APP_PACKAGE);
416c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        ALLOWED_IN_EXCEPTION.add(Events.CUSTOM_APP_URI);
417501e60bcb1b519d80723f7b64ba60bd079b8ec8dSara Ting        ALLOWED_IN_EXCEPTION.add(Events.UID_2445);
418bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // deleted, original_id, alerts
419bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
420bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
421bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** Don't clone these from the base event into the exception event. */
422bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final String[] DONT_CLONE_INTO_EXCEPTION = {
423bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events._SYNC_ID,
424bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA1,
42502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA2,
42602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA3,
42702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA4,
42802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA5,
42902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA6,
430bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Events.SYNC_DATA7,
43102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        Events.SYNC_DATA8,
432c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA9,
433c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        Events.SYNC_DATA10,
434bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    };
435bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
436bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /** set to 'true' to enable debug logging for recurrence exception code */
437bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final boolean DEBUG_EXCEPTION = false;
438bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
439dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private Context mContext;
440e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    private ContentResolver mContentResolver;
441e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
4428bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    private static CalendarProvider2 mInstance;
4438bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
444420b7fb569773ae573fbe90c3a9c522d4c368863Erik    @VisibleForTesting
445420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected CalendarAlarmManager mCalendarAlarm;
446a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
447a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private final Handler mBroadcastHandler = new Handler() {
448a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        @Override
449a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        public void handleMessage(Message msg) {
450dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            Context context = CalendarProvider2.this.mContext;
451a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            if (msg.what == UPDATE_BROADCAST_MSG) {
452a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                // Broadcast a provider changed intent
453a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                doSendUpdateNotification();
454dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // Because the handler does not guarantee message delivery in
455dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // the case that the provider is killed, we need to make sure
456dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // that the provider stays alive long enough to deliver the
457dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // notification. This empty service is sufficient to "wedge" the
458dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                // process until we stop it here.
459a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang                context.stopService(new Intent(context, EmptyService.class));
460a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            }
461a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        }
462a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    };
4639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
4659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Listens for timezone changes and disk-no-longer-full events
4669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
4689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
4699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void onReceive(Context context, Intent intent) {
4709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String action = intent.getAction();
4719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Log.isLoggable(TAG, Log.DEBUG)) {
4729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.d(TAG, "onReceive() " + action);
4739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
4759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
476ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
4779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
4789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Try to clean up if things were screwy due to a full disk
4799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateTimezoneDependentFields();
480ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
4819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
482ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
4839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
4849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
4859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    };
4869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /* Visible for testing */
4889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
4899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected CalendarDatabaseHelper getDatabaseHelper(final Context context) {
4909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return CalendarDatabaseHelper.getInstance(context);
4919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
4929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4938bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    protected static CalendarProvider2 getInstance() {
4948bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        return mInstance;
4958bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
4968bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
497e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    @Override
498e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    public void shutdown() {
499e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        if (mDbHelper != null) {
500e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper.close();
501e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDbHelper = null;
502e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio            mDb = null;
503e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
5048bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio    }
5058bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
5079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public boolean onCreate() {
5089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        super.onCreate();
509043587d3ef1a9cc156a6819fdcb7ef5b2aa81ed4Dianne Hackborn        setAppOps(AppOpsManager.OP_READ_CALENDAR, AppOpsManager.OP_WRITE_CALENDAR);
510ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
511ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return initialize();
512ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (RuntimeException e) {
513f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
514f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot start provider", e);
515f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
516ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return false;
517ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
518ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
5199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
520ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private boolean initialize() {
5218bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio        mInstance = this;
5228bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
523dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mContext = getContext();
524e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContentResolver = mContext.getContentResolver();
525e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
526ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDbHelper = (CalendarDatabaseHelper)getDatabaseHelper();
527ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        mDb = mDbHelper.getWritableDatabase();
5289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5294caf8d015918f619a67d321a152f150a01022717Andy McFadden        mMetaData = new MetaData(mDbHelper);
5304caf8d015918f619a67d321a152f150a01022717Andy McFadden        mInstancesHelper = new CalendarInstancesHelper(mDbHelper, mMetaData);
5314caf8d015918f619a67d321a152f150a01022717Andy McFadden
5329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Register for Intent broadcasts
5339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        IntentFilter filter = new IntentFilter();
5349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
5369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
5379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        filter.addAction(Intent.ACTION_TIME_CHANGED);
5389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't ever unregister this because this thread always wants
5409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // to receive notifications, even in the background.  And if this
5419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // thread is killed then the whole process will be killed and the
5429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // memory resources will be reclaimed.
543e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.registerReceiver(mIntentReceiver, filter);
5449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
545ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mCalendarCache = new CalendarCache(mDbHelper);
546ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
547420b7fb569773ae573fbe90c3a9c522d4c368863Erik        // This is pulled out for testing
548420b7fb569773ae573fbe90c3a9c522d4c368863Erik        initCalendarAlarm();
549e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
550e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        postInitialize();
5518bb142159463f654ef07e20a341fcb527f0109f2Fabrice Di Meglio
5529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return true;
5539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
5549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
555420b7fb569773ae573fbe90c3a9c522d4c368863Erik    protected void initCalendarAlarm() {
556420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm = getOrCreateCalendarAlarmManager();
557e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
558e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
559e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    synchronized CalendarAlarmManager getOrCreateCalendarAlarmManager() {
560420b7fb569773ae573fbe90c3a9c522d4c368863Erik        if (mCalendarAlarm == null) {
561420b7fb569773ae573fbe90c3a9c522d4c368863Erik            mCalendarAlarm = new CalendarAlarmManager(mContext);
56298e01573880a2f1b1547d1e2e9a1c5c7d5e09043Alon Albert            Log.i(TAG, "Created " + mCalendarAlarm + "(" + this + ")");
563e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        }
564420b7fb569773ae573fbe90c3a9c522d4c368863Erik        return mCalendarAlarm;
565e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio    }
566e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio
567ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    protected void postInitialize() {
568ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        Thread thread = new PostInitializeThread();
569ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        thread.start();
570ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
571ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
572ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    private class PostInitializeThread extends Thread {
573ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        @Override
574ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        public void run() {
575ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
576ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
577ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            verifyAccounts();
578ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
579c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            try {
580c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                doUpdateTimezoneDependentFields();
581c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            } catch (IllegalStateException e) {
582c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // Added this because tests would fail if the provider is
583c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // closed by the time this is executed
584c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan
585c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan                // Nothing actionable here anyways.
586c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan            }
587ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
588ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio    }
589ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio
59064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void verifyAccounts() {
59164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountManager.get(getContext()).addOnAccountsUpdatedListener(this, null, false);
59264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        removeStaleAccounts(AccountManager.get(getContext()).getAccounts());
59364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
59464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
59564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
5969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
5979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This creates a background thread to check the timezone and update
5989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the timezone dependent fields in the Instances table if the timezone
599315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * has changed.
6009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
6019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected void updateTimezoneDependentFields() {
6029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Thread thread = new TimezoneCheckerThread();
6039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        thread.start();
6049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private class TimezoneCheckerThread extends Thread {
6079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        @Override
6089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        public void run() {
6099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
610ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            doUpdateTimezoneDependentFields();
6119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
6129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
6139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
6149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
615315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * Check if we are in the same time zone
616315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     */
617315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isLocalSameAsInstancesTimezone() {
618315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String localTimezone = TimeZone.getDefault().getID();
619315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        return TextUtils.equals(mCalendarCache.readTimezoneInstances(), localTimezone);
620315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
621315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
622315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    /**
6239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * This method runs in a background thread.  If the timezone has changed
6249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * then the Instances table will be regenerated.
6259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
626315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doUpdateTimezoneDependentFields() {
627ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        try {
628315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String timezoneType = mCalendarCache.readTimezoneType();
629315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // Nothing to do if we have the "home" timezone type (timezone is sticky)
630a637bc824d92888eec9c6d2da0d5f1e594bebebaFabrice Di Meglio            if (timezoneType != null && timezoneType.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
631315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return;
632315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
633315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            // We are here in "auto" mode, the timezone is coming from the device
634ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            if (! isSameTimezoneDatabaseVersion()) {
635315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String localTimezone = TimeZone.getDefault().getID();
636315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                doProcessEventRawTimes(localTimezone, TimeUtils.getTimeZoneDatabaseVersion());
637ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
638315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            if (isLocalSameAsInstancesTimezone()) {
639ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Even if the timezone hasn't changed, check for missed alarms.
640ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // This code executes when the CalendarProvider2 is created and
641ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // helps to catch missed alarms when the Calendar process is
642ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // killed (because of low-memory conditions) and then restarted.
643420b7fb569773ae573fbe90c3a9c522d4c368863Erik                mCalendarAlarm.rescheduleMissedAlarms();
644ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
645ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        } catch (SQLException e) {
646f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
647f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "doUpdateTimezoneDependentFields() failed", e);
648f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
649ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            try {
650ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Clear at least the in-memory data (and if possible the
651ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // database fields) to force a re-computation of Instances.
652ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                mMetaData.clearInstanceRange();
653ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            } catch (SQLException e2) {
654f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.ERROR)) {
655f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.e(TAG, "clearInstanceRange() also failed: " + e2);
656f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
657ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            }
6589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
659ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
660ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
661315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    protected void doProcessEventRawTimes(String localTimezone, String timeZoneDatabaseVersion) {
662ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        mDb.beginTransaction();
663ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
6643443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio            updateEventsStartEndFromEventRawTimesLocked();
665ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            updateTimezoneDatabaseVersion(timeZoneDatabaseVersion);
666315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mCalendarCache.writeTimezoneInstances(localTimezone);
667ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            regenerateInstancesTable();
668ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.setTransactionSuccessful();
669ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
670ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mDb.endTransaction();
671ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
672ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
673ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
6743443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio    private void updateEventsStartEndFromEventRawTimesLocked() {
6753443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio        Cursor cursor = mDb.rawQuery(SQL_SELECT_EVENTSRAWTIMES, null /* selection args */);
676ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
677ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            while (cursor.moveToNext()) {
678ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                long eventId = cursor.getLong(0);
679ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtStart2445 = cursor.getString(1);
680ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                String dtEnd2445 = cursor.getString(2);
6813443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                String eventTimezone = cursor.getString(3);
682f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (dtStart2445 == null && dtEnd2445 == null) {
683f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.ERROR)) {
684f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.e(TAG, "Event " + eventId + " has dtStart2445 and dtEnd2445 null "
685f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                + "at the same time in EventsRawTimes!");
686f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
687f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    continue;
688f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
689ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                updateEventsStartEndLocked(eventId,
6903443e3ebeaa39e8415b43e7cf3b218caee554e9bFabrice Di Meglio                        eventTimezone,
691ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtStart2445,
692ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                        dtEnd2445);
693ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
694ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } finally {
695ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor.close();
696ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            cursor = null;
697ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
698ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
699ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
700ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private long get2445ToMillis(String timezone, String dt2445) {
701ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (null == dt2445) {
702f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
703f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.v(TAG, "Cannot parse null RFC2445 date");
704f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
705ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
706ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
707ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        Time time = (timezone != null) ? new Time(timezone) : new Time();
708ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
709ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            time.parse(dt2445);
710ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (TimeFormatException e) {
711f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
712f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Cannot parse RFC2445 date " + dt2445);
713f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
714ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return 0;
715ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
716ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return time.toMillis(true /* ignore DST */);
717ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
718ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
719ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateEventsStartEndLocked(long eventId,
720ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            String timezone, String dtStart2445, String dtEnd2445) {
721ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
722ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        ContentValues values = new ContentValues();
723b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTSTART, get2445ToMillis(timezone, dtStart2445));
724b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        values.put(Events.DTEND, get2445ToMillis(timezone, dtEnd2445));
725ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
726b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        int result = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
727dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                new String[] {String.valueOf(eventId)});
728ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (0 == result) {
729ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
730ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio                Log.v(TAG, "Could not update Events table with values " + values);
731ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            }
732ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
733ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
734ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
735ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void updateTimezoneDatabaseVersion(String timeZoneDatabaseVersion) {
736ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        try {
737ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            mCalendarCache.writeTimezoneDatabaseVersion(timeZoneDatabaseVersion);
738ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        } catch (CalendarCache.CacheException e) {
739f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
740f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Could not write timezone database version in the cache");
741f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
742ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
743ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
7449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
745ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    /**
746ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     * Check if the time zone database version is the same as the cached one
747ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio     */
748ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected boolean isSameTimezoneDatabaseVersion() {
749315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
750315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
751ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return false;
752ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
753ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return TextUtils.equals(timezoneDatabaseVersion, TimeUtils.getTimeZoneDatabaseVersion());
754ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
755ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
75625e5cdec4e39982fedcce0733d2b8ad1aa665b19Fabrice Di Meglio    @VisibleForTesting
757ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    protected String getTimezoneDatabaseVersion() {
758315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String timezoneDatabaseVersion = mCalendarCache.readTimezoneDatabaseVersion();
759315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (timezoneDatabaseVersion == null) {
760ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio            return "";
761ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        }
762f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
763f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "timezoneDatabaseVersion = " + timezoneDatabaseVersion);
764f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
765ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        return timezoneDatabaseVersion;
766ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    }
767ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio
768315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private boolean isHomeTimezone() {
7690ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        final String type = mCalendarCache.readTimezoneType();
7700ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        return CalendarCache.TIMEZONE_TYPE_HOME.equals(type);
771315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    }
772315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
773ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio    private void regenerateInstancesTable() {
7749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // The database timezone is different from the current timezone.
7759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Regenerate the Instances table for this month.  Include events
7769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // starting at the beginning of this month.
7779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long now = System.currentTimeMillis();
778315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone = mCalendarCache.readTimezoneInstances();
779315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
7809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.set(now);
7819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.monthDay = 1;
7829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.hour = 0;
7839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.minute = 0;
7849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.second = 0;
7851f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long begin = time.normalize(true);
7879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long end = begin + MINIMUM_EXPANSION_SPAN;
7881f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio
7891f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        Cursor cursor = null;
7901f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        try {
7911f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            cursor = handleInstanceQuery(new SQLiteQueryBuilder(),
7921f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    begin, end,
7931f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                    new String[] { Instances._ID },
7942ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* selection */, null,
7952ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    null /* sort */,
796d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio                    false /* searchByDayInsteadOfMillis */,
797315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    true /* force Instances deletion and expansion */,
7982ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                    instancesTimezone, isHomeTimezone());
7991f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        } finally {
8001f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            if (cursor != null) {
8011f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio                cursor.close();
8021f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio            }
8031f755da44aeecdc84d0e957d55178f942dfdb15dFabrice Di Meglio        }
8049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
805420b7fb569773ae573fbe90c3a9c522d4c368863Erik        mCalendarAlarm.rescheduleMissedAlarms();
8069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
810b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected void notifyChange(boolean syncToNetwork) {
8119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Note that semantics are changed: notification is for CONTENT_URI, not the specific
8129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Uri that was modified.
813b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        mContentResolver.notifyChange(CalendarContract.CONTENT_URI, null, syncToNetwork);
8149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
8159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
816a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert    /**
817a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert     * ALERT table is maintained locally so don't request a sync for changes in it
818a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert     */
819a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert    @Override
820a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert    protected boolean shouldSyncFor(Uri uri) {
821a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert        final int match = sUriMatcher.match(uri);
822a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert        return !(match == CALENDAR_ALERTS ||
823a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert                match == CALENDAR_ALERTS_ID ||
824a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert                match == CALENDAR_ALERTS_BY_INSTANCE);
825a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert    }
826a55c3aaa2b13b5350918efda98906afb8b6206c1Alon Albert
8279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
8289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
8299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String sortOrder) {
83096d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein        final long identity = clearCallingIdentityInternal();
8318015a4086559d9d025473c2e773b2f64888c2942Alon Albert        try {
8328015a4086559d9d025473c2e773b2f64888c2942Alon Albert            return queryInternal(uri, projection, selection, selectionArgs, sortOrder);
8338015a4086559d9d025473c2e773b2f64888c2942Alon Albert        } finally {
83496d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein            restoreCallingIdentityInternal(identity);
8358015a4086559d9d025473c2e773b2f64888c2942Alon Albert        }
8368015a4086559d9d025473c2e773b2f64888c2942Alon Albert    }
8378015a4086559d9d025473c2e773b2f64888c2942Alon Albert
8388015a4086559d9d025473c2e773b2f64888c2942Alon Albert    private Cursor queryInternal(Uri uri, String[] projection, String selection,
8398015a4086559d9d025473c2e773b2f64888c2942Alon Albert            String[] selectionArgs, String sortOrder) {
840ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
841ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query uri - " + uri);
8429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
8438d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
8449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
8459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
8479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String groupBy = null;
8489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String limit = null; // Not currently implemented
849315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        String instancesTimezone;
8509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
8529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
8539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
854fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selection, selectionArgs,
8559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        sortOrder);
856fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden            case SYNCSTATE_ID:
857fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                String selectionWithId = (SyncState._ID + "=?")
858fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                    + (selection == null ? "" : " AND (" + selection + ")");
859fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                // Prepend id to selectionArgs
860fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                selectionArgs = insertSelectionArg(selectionArgs,
861fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        String.valueOf(ContentUris.parseId(uri)));
862fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                return mDbHelper.getSyncState().query(db, projection, selectionWithId,
863fe1cb130bba78b36292a64d7c0bfb3292738973cAndy McFadden                        selectionArgs, sortOrder);
8649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
8659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
8661ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
8688d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8698d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8709ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
8719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
8729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
8731ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
8749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sEventsProjectionMap);
875636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
876b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
8779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
87819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
87919fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES:
88019fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
88119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
8828d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8838d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                selection = appendLastSyncedColumnToSelection(selection, uri);
88519fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
88619fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana            case EVENT_ENTITIES_ID:
88719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
88819fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                qb.setProjectionMap(sEventEntitiesProjectionMap);
889636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
890b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
89119fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana                break;
89219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
8932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
8942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setTables(Tables.COLORS);
8952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                qb.setProjectionMap(sColorsProjectionMap);
8968d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
8978d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
8982f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
8992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
9009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
90143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES:
902b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
903b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                qb.setProjectionMap(sCalendarsProjectionMap);
9048d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
9058d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
9069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
90843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDAR_ENTITIES_ID:
909b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDARS);
910b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                qb.setProjectionMap(sCalendarsProjectionMap);
911636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
912b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ID);
9139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
9159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
9169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long begin;
9179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long end;
9189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    begin = Long.valueOf(uri.getPathSegments().get(2));
9209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse begin "
9229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
9239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    end = Long.valueOf(uri.getPathSegments().get(3));
9269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end "
9289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
930315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
9312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceQuery(qb, begin, end, projection, selection, selectionArgs,
9322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        sortOrder, match == INSTANCES_BY_DAY, false /* don't force an expansion */,
933315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
93481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH:
93581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            case INSTANCES_SEARCH_BY_DAY:
93681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
93781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    begin = Long.valueOf(uri.getPathSegments().get(2));
93881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
93981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse begin "
94081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(2));
94181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
94281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                try {
94381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    end = Long.valueOf(uri.getPathSegments().get(3));
94481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                } catch (NumberFormatException nfe) {
94581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    throw new IllegalArgumentException("Cannot parse end "
94681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                            + uri.getPathSegments().get(3));
94781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
948315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
94981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                // this is already decoded
95081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                String query = uri.getPathSegments().get(4);
9512ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                return handleInstanceSearchQuery(qb, begin, end, query, projection, selection,
9522ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                        selectionArgs, sortOrder, match == INSTANCES_SEARCH_BY_DAY,
953315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9546db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
9559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int startDay;
9569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                int endDay;
9579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    startDay = Integer.valueOf(uri.getPathSegments().get(2));
9599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse start day "
9619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(2));
9629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
9639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
9649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    endDay = Integer.valueOf(uri.getPathSegments().get(3));
9659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } catch (NumberFormatException nfe) {
9669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Cannot parse end day "
9679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + uri.getPathSegments().get(3));
9689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
969315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                instancesTimezone = mCalendarCache.readTimezoneInstances();
970315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return handleEventDayQuery(qb, startDay, endDay, projection, selection,
971315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        instancesTimezone, isHomeTimezone());
9729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
97302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
975ef1f983b14a586f579a0d2978a0b0ccc2fcc425cMichael Chan                qb.appendWhere(SQL_WHERE_ATTENDEE_BASE);
9769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
97802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.ATTENDEES + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sAttendeesProjectionMap);
980636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
981b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_ATTENDEES_ID);
9829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
984b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.REMINDERS);
9859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
98702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik                qb.setTables(Tables.REMINDERS + ", " + Tables.EVENTS + ", " + Tables.CALENDARS);
9889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sRemindersProjectionMap);
989636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
990b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_REMINDERS_ID);
9919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
993b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
995b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
9969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
9979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
998b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
9999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
1000b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT);
10019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                groupBy = CalendarAlerts.EVENT_ID + "," + CalendarAlerts.BEGIN;
10029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
10039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
1004b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_ALERTS + ", " + CalendarDatabaseHelper.Views.EVENTS);
10059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                qb.setProjectionMap(sCalendarAlertsProjectionMap);
1006636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
1007b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_CALENDAR_ALERT_ID);
10089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
10099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES:
1010b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
10119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
10129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
1013b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.EXTENDED_PROPERTIES);
1014636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
1015b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.appendWhere(SQL_WHERE_EXTENDED_PROPERTIES_ID);
10169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
1017315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1018b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                qb.setTables(Tables.CALENDAR_CACHE);
1019315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                qb.setProjectionMap(sCalendarCacheProjectionMap);
1020315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                break;
10219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
10229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
10239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // run the query
10269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit);
10279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10298d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private void validateUriParameters(Set<String> queryParameterNames) {
10308d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        final Set<String> parameterNames = queryParameterNames;
10318d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        for (String parameterName : parameterNames) {
10328d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            if (!ALLOWED_URI_PARAMETERS.contains(parameterName)) {
10338d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                throw new IllegalArgumentException("Invalid URI parameter: " + parameterName);
10348d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            }
10358d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        }
10368d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    }
10378d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert
10389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
10399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String selection, String[] selectionArgs, String sortOrder, String groupBy,
10409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String limit) {
1041ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio
104239c65e5716e21e863d8de587d139dae85f99422fFred Quintana        if (projection != null && projection.length == 1
104339c65e5716e21e863d8de587d139dae85f99422fFred Quintana                && BaseColumns._COUNT.equals(projection[0])) {
104439c65e5716e21e863d8de587d139dae85f99422fFred Quintana            qb.setProjectionMap(sCountProjectionMap);
104539c65e5716e21e863d8de587d139dae85f99422fFred Quintana        }
104639c65e5716e21e863d8de587d139dae85f99422fFred Quintana
1047ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1048ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio            Log.v(TAG, "query sql - projection: " + Arrays.toString(projection) +
1049ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selection: " + selection +
1050ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " selectionArgs: " + Arrays.toString(selectionArgs) +
1051ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " sortOrder: " + sortOrder +
1052ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " groupBy: " + groupBy +
1053ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio                    " limit: " + limit);
1054ab42ec67e77c398ac94ff1cf561fadd9f6b48dcbFabrice Di Meglio        }
10559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
10569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                sortOrder, limit);
10579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c != null) {
10589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // TODO: is this the right notification Uri?
1059b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            c.setNotificationUri(mContentResolver, CalendarContract.Events.CONTENT_URI);
10609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
10619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return c;
10629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
10639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
10649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /*
10659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Fills the Instances table, if necessary, for the given range and then
10669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * queries the Instances table.
10679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
10689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param qb The query
10699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeBegin start of range (Julian days or ms)
10709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param rangeEnd end of range (Julian days or ms)
10719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param projection The projection
10729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param selection The selection
10739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param sort How to sort
10749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param searchByDay if true, range is in Julian days, if false, range is in ms
1075d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1076315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1077315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
10789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return
10799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
10809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private Cursor handleInstanceQuery(SQLiteQueryBuilder qb, long rangeBegin,
10812ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            long rangeEnd, String[] projection, String selection, String[] selectionArgs,
10822ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String sort, boolean searchByDay, boolean forceExpansion,
10832ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
10840ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
108581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
10869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        qb.setProjectionMap(sInstancesProjectionMap);
10879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (searchByDay) {
10889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Convert the first and last Julian day range to a range that uses
10899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // UTC milliseconds.
1090315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
10919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long beginMs = time.setJulianDay((int) rangeBegin);
10929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // We add one to lastDay because the time is set to 12am on the given
10939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Julian day and we want to include all the events on the last day.
10949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long endMs = time.setJulianDay((int) rangeEnd + 1);
10959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1096315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */,
1097315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1098b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
10999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
11009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // will lock the database.
1101315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */,
1102315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
1103b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
11049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
11052ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11062ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        String[] newSelectionArgs = new String[] {String.valueOf(rangeEnd),
11078335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff                String.valueOf(rangeBegin)};
11082ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
11092ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = newSelectionArgs;
11102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
11112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            selectionArgs = combine(newSelectionArgs, selectionArgs);
11122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11138335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
11147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                null /* having */, sort);
11159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
11169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
111781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
11182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * Combine a set of arrays in the order they are passed in. All arrays must
11192ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     * be of the same type.
11202ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik     */
11212ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static <T> T[] combine(T[]... arrays) {
11222ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (arrays.length == 0) {
11232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            throw new IllegalArgumentException("Must supply at least 1 array to combine");
11242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int totalSize = 0;
11272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
11282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            totalSize += array.length;
11292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        T[] finalArray = (T[]) (Array.newInstance(arrays[0].getClass().getComponentType(),
11322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                totalSize));
11332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11342ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        int currentPos = 0;
11352ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        for (T[] array : arrays) {
11362ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            int length = array.length;
11372ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            System.arraycopy(array, 0, finalArray, currentPos, length);
11382ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            currentPos += array.length;
11392ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
11402ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        return finalArray;
11412ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    }
11422ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik
11432ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    /**
1144dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * Escape any special characters in the search token
1145dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @param token the token to escape
1146dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * @return the escaped token
1147dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     */
1148dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    @VisibleForTesting
1149dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    String escapeSearchToken(String token) {
1150dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_ESCAPE_PATTERN.matcher(token);
1151dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matcher.replaceAll(SEARCH_ESCAPE_CHAR + "$1");
1152dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    }
1153dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
1154dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang    /**
115581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * Splits the search query into individual search tokens based on whitespace
1156dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * and punctuation. Leaves both single quoted and double quoted strings
1157dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang     * intact.
115881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *
115981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @param query the search query
116081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * @return an array of tokens from the search query
116181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
116281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
116381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String[] tokenizeSearchQuery(String query) {
1164dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        List<String> matchList = new ArrayList<String>();
1165dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        Matcher matcher = SEARCH_TOKEN_PATTERN.matcher(query);
1166dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String token;
1167dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        while (matcher.find()) {
1168dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            if (matcher.group(1) != null) {
1169dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // double quoted string
1170dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group(1);
1171dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            } else {
1172dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                // unquoted token
1173dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                token = matcher.group();
1174dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            }
1175dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang            matchList.add(escapeSearchToken(token));
1176dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        }
1177dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        return matchList.toArray(new String[matchList.size()]);
117881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
117981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
118081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    /**
118181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * In order to support what most people would consider a reasonable
118281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * search behavior, we have to do some interesting things here. We
118381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * assume that when a user searches for something like "lunch meeting",
118481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * they really want any event that matches both "lunch" and "meeting",
118581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * not events that match the string "lunch meeting" itself. In order to
118681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * do this across multiple columns, we have to construct a WHERE clause
118781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * that looks like:
118881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * <code>
118981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *   WHERE (title LIKE "%lunch%"
119081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%lunch%"
119181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%lunch%")
119281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *     AND (title LIKE "%meeting%"
119381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR description LIKE "%meeting%"
119481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     *      OR eventLocation LIKE "%meeting%")
119581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * </code>
119681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     * This "product of clauses" is a bit ugly, but produced a fairly good
1197cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * approximation of full-text search across multiple columns.  The set
1198cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * of columns is specified by the SEARCH_COLUMNS constant.
1199cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * <p>
1200cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * Note the "WHERE" token isn't part of the returned string.  The value
1201cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden     * may be passed into a query as the "HAVING" clause.
120281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang     */
120381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
120481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    String constructSearchWhere(String[] tokens) {
120581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (tokens.length == 0) {
120681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            return "";
120781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
120881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        StringBuilder sb = new StringBuilder();
120981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        String column, token;
121081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
121181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            sb.append("(");
121281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
121381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                sb.append(SEARCH_COLUMNS[i]);
1214dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(" LIKE ? ESCAPE \"");
1215dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append(SEARCH_ESCAPE_CHAR);
1216dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang                sb.append("\" ");
121781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                if (i < SEARCH_COLUMNS.length - 1) {
121881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                    sb.append("OR ");
121981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                }
122081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
122118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            sb.append(")");
122218f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            if (j < tokens.length - 1) {
122318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                sb.append(" AND ");
122418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            }
122581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
122681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return sb.toString();
122781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
122881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
122981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    @VisibleForTesting
1230ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak    String[] constructSearchArgs(String[] tokens) {
123118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        int numCols = SEARCH_COLUMNS.length;
1232ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak        int numArgs = tokens.length * numCols;
123318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        String[] selectionArgs = new String[numArgs];
123481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        for (int j = 0; j < tokens.length; j++) {
1235ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak            int start = numCols * j;
1236f50ca85e25d0e450b9f2ad78ee37870294462d4cMason Tang            for (int i = start; i < start + numCols; i++) {
123718f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang                selectionArgs[i] = "%" + tokens[j] + "%";
123881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            }
123981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
124081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        return selectionArgs;
124181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
124281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
124381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
124481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long rangeBegin, long rangeEnd, String query, String[] projection,
12452ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String selection, String[] selectionArgs, String sort, boolean searchByDay,
12462ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik            String instancesTimezone, boolean isHomeTimezone) {
12470ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
124818f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
124981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setProjectionMap(sInstancesProjectionMap);
125081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
1251dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String[] tokens = tokenizeSearchQuery(query);
1252ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak        String[] searchArgs = constructSearchArgs(tokens);
1253ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak        String[] timeRange = new String[] {String.valueOf(rangeEnd), String.valueOf(rangeBegin)};
12542ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        if (selectionArgs == null) {
1255ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak            selectionArgs = combine(timeRange, searchArgs);
12562ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        } else {
1257ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak            // where clause comes first, so put selectionArgs before searchArgs.
1258ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak            selectionArgs = combine(timeRange, selectionArgs, searchArgs);
12592ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik        }
126018f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // we pass this in as a HAVING instead of a WHERE so the filtering
126118f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        // happens after the grouping
1262dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang        String searchWhere = constructSearchWhere(tokens);
1263dc866a1a66871a55810cbf98169f3212fb47acd3Mason Tang
126481d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        if (searchByDay) {
126581d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Convert the first and last Julian day range to a range that uses
126681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // UTC milliseconds.
1267315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Time time = new Time(instancesTimezone);
126881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long beginMs = time.setJulianDay((int) rangeBegin);
126981d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // We add one to lastDay because the time is set to 12am on the given
127081d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // Julian day and we want to include all the events on the last day.
127181d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            long endMs = time.setJulianDay((int) rangeEnd + 1);
127281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
127318f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
127418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
127556292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(beginMs, endMs,
127656292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1277315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1278315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1279315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
128056292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1281b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
128281d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        } else {
128381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang            // will lock the database.
128418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // we expand the instances here because we might be searching over
128518f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang            // a range where instance expansion has not occurred yet
128656292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            acquireInstanceRange(rangeBegin, rangeEnd,
128756292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio                    true /* use minimum expansion window */,
1288315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    false /* do not force Instances deletion and expansion */,
1289315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    instancesTimezone,
1290315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    isHomeTimezone
129156292bcd683034ea05dd407ed15cebb70f954210Fabrice Di Meglio            );
1292b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN);
129381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        }
129418f75d6fe8dd0b4fb1deb5e56b4356ae6527bdbcMason Tang        return qb.query(mDb, projection, selection, selectionArgs,
1295c3780839fd044b5d8109860b57a199a2da1d804fMichael Chan                Tables.INSTANCES + "." + Instances._ID /* groupBy */,
1296cad6bc946434363f6ba6fed58bfa818cd6736d21Andy McFadden                searchWhere /* having */, sort);
129781d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang    }
129881d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang
12996db535b458146a279bebd4a51d56c1bdfc204528Erik    private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
1300315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String[] projection, String selection, String instancesTimezone,
1301315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean isHomeTimezone) {
13020ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
130381d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang        qb.setTables(INSTANCE_QUERY_TABLES);
13046db535b458146a279bebd4a51d56c1bdfc204528Erik        qb.setProjectionMap(sInstancesProjectionMap);
130543556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Convert the first and last Julian day range to a range that uses
130643556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // UTC milliseconds.
1307315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        Time time = new Time(instancesTimezone);
1308192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long beginMs = time.setJulianDay(begin);
130943556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // We add one to lastDay because the time is set to 12am on the given
131043556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff        // Julian day and we want to include all the events on the last day.
1311192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        long endMs = time.setJulianDay(end + 1);
131243556fa5610bd302cb80aa5ddc98af1e2f2d8b18Ken Shirriff
1313315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        acquireInstanceRange(beginMs, endMs, true,
1314315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                false /* do not force Instances expansion */, instancesTimezone, isHomeTimezone);
1315b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        qb.appendWhere(SQL_WHERE_INSTANCES_BETWEEN_DAY);
13168335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
13178335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff
13188335a18ac6024f302b50e6f473ad4058cc355c85Ken Shirriff        return qb.query(mDb, projection, selection, selectionArgs,
13196db535b458146a279bebd4a51d56c1bdfc204528Erik                Instances.START_DAY /* groupBy */, null /* having */, null);
13209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
13249ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * table.  Acquires the database lock and calls
13259ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #acquireInstanceRangeLocked(long, long, boolean, boolean, String, boolean)}.
13269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
13289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
13299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1330d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1331315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1332315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
13339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1334d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio    private void acquireInstanceRange(final long begin, final long end,
1335315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final boolean useMinimumExpansionWindow, final boolean forceExpansion,
1336315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            final String instancesTimezone, final boolean isHomeTimezone) {
13379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
13389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
1339315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            acquireInstanceRangeLocked(begin, end, useMinimumExpansionWindow,
1340315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    forceExpansion, instancesTimezone, isHomeTimezone);
13419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
13429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
13439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
13449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
13469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
13489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Ensure that the date range given has all elements in the instance
13499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * table.  The database lock must be held when calling this method.
13509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
13519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param begin start of range (ms)
13529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param end end of range (ms)
13539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param useMinimumExpansionWindow expand by at least MINIMUM_EXPANSION_SPAN
1354315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param forceExpansion force the Instance deletion and expansion if set to true
1355315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param instancesTimezone timezone we need to use for computing the instances
1356315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio     * @param isHomeTimezone if true, we are in the "home" timezone
13579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
1358420b7fb569773ae573fbe90c3a9c522d4c368863Erik    void acquireInstanceRangeLocked(long begin, long end, boolean useMinimumExpansionWindow,
1359315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            boolean forceExpansion, String instancesTimezone, boolean isHomeTimezone) {
13609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandBegin = begin;
13619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long expandEnd = end;
13629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1363d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (DEBUG_INSTANCES) {
1364d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            Log.d(TAG + "-i", "acquireInstanceRange begin=" + begin + " end=" + end +
1365d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    " useMin=" + useMinimumExpansionWindow + " force=" + forceExpansion);
1366d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1367d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1368315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (instancesTimezone == null) {
1369315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            Log.e(TAG, "Cannot run acquireInstanceRangeLocked() because instancesTimezone is null");
1370315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            return;
1371315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1372315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
13739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (useMinimumExpansionWindow) {
13749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // if we end up having to expand events into the instances table, expand
13759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // events for a minimal amount of time, so we do not have to perform
13769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // expansions frequently.
13779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long span = end - begin;
13789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (span < MINIMUM_EXPANSION_SPAN) {
13799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long additionalRange = (MINIMUM_EXPANSION_SPAN - span) / 2;
13809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandBegin -= additionalRange;
13819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                expandEnd += additionalRange;
13829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
13839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
13849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
13859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Check if the timezone has changed.
13869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We do this check here because the database is locked and we can
13879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // safely delete all the entries in the Instances table.
13889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        MetaData.Fields fields = mMetaData.getFieldsLocked();
13899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long maxInstance = fields.maxInstance;
13909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long minInstance = fields.minInstance;
1391315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        boolean timezoneChanged;
1392315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        if (isHomeTimezone) {
1393315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String previousTimezone = mCalendarCache.readTimezoneInstancesPrevious();
1394315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(previousTimezone);
1395315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        } else {
1396315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            String localTimezone = TimeZone.getDefault().getID();
1397315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            timezoneChanged = !instancesTimezone.equals(localTimezone);
13987be45683e367bd6897daf6444b03be938f8f5eaaErik            // if we're in auto make sure we are using the device time zone
13997be45683e367bd6897daf6444b03be938f8f5eaaErik            if (timezoneChanged) {
14007be45683e367bd6897daf6444b03be938f8f5eaaErik                instancesTimezone = localTimezone;
14017be45683e367bd6897daf6444b03be938f8f5eaaErik            }
1402315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        }
1403315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "home", then timezoneChanged only if current != previous
1404315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // if "auto", then timezoneChanged, if !instancesTimezone.equals(localTimezone);
1405d69a1a64027cd5937c7db622aaf7af493e6d3610Fabrice Di Meglio        if (maxInstance == 0 || timezoneChanged || forceExpansion) {
1406d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1407d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "Wiping instances and expanding from scratch");
1408d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1409d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
14109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Empty the Instances table and expand from scratch.
1411b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio            mDb.execSQL("DELETE FROM " + Tables.INSTANCES + ";");
1412f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
14136db535b458146a279bebd4a51d56c1bdfc204528Erik                Log.v(TAG, "acquireInstanceRangeLocked() deleted Instances,"
14149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + " timezone changed: " + timezoneChanged);
14159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
1416f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, expandEnd, instancesTimezone);
1417315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
1418315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            mMetaData.writeLocked(instancesTimezone, expandBegin, expandEnd);
14199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14200ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner            final String timezoneType = mCalendarCache.readTimezoneType();
14217be45683e367bd6897daf6444b03be938f8f5eaaErik            // This may cause some double writes but guarantees the time zone in
14227be45683e367bd6897daf6444b03be938f8f5eaaErik            // the db and the time zone the instances are in is the same, which
14237be45683e367bd6897daf6444b03be938f8f5eaaErik            // future changes may affect.
14247be45683e367bd6897daf6444b03be938f8f5eaaErik            mCalendarCache.writeTimezoneInstances(instancesTimezone);
14257be45683e367bd6897daf6444b03be938f8f5eaaErik
14267be45683e367bd6897daf6444b03be938f8f5eaaErik            // If we're in auto check if we need to fix the previous tz value
14270ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner            if (CalendarCache.TIMEZONE_TYPE_AUTO.equals(timezoneType)) {
14287be45683e367bd6897daf6444b03be938f8f5eaaErik                String prevTZ = mCalendarCache.readTimezoneInstancesPrevious();
14297be45683e367bd6897daf6444b03be938f8f5eaaErik                if (TextUtils.equals(TIMEZONE_GMT, prevTZ)) {
14307be45683e367bd6897daf6444b03be938f8f5eaaErik                    mCalendarCache.writeTimezoneInstancesPrevious(instancesTimezone);
14317be45683e367bd6897daf6444b03be938f8f5eaaErik                }
1432315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
14339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the desired range [begin, end] has already been
14379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // expanded, then simply return.  The range is inclusive, that is,
14389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events that touch either endpoint are included in the expansion.
14399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // This means that a zero-duration event that starts and ends at
14409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // the endpoint will be included.
14419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We use [begin, end] here and not [expandBegin, expandEnd] for
14429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // checking the range because a common case is for the client to
14439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // request successive days or weeks, for example.  If we checked
14449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // that the expanded range [expandBegin, expandEnd] then we would
14459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // always be expanding because there would always be one more day
14469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // or week that hasn't been expanded.
14479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if ((begin >= minInstance) && (end <= maxInstance)) {
1448d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (DEBUG_INSTANCES) {
1449d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG + "-i", "instances are already expanded");
1450d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
1451f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.VERBOSE)) {
14529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Log.v(TAG, "Canceled instance query (" + expandBegin + ", " + expandEnd
14539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + ") falls within previously expanded range.");
14549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
14559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
14569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested begin point has not been expanded, then include
14599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandBegin").
14609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (begin < minInstance) {
1461f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(expandBegin, minInstance, instancesTimezone);
14629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            minInstance = expandBegin;
14639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the requested end point has not been expanded, then include
14669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // more events than requested in the expansion (use "expandEnd").
14679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (end > maxInstance) {
1468f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik            mInstancesHelper.expandInstanceRangeLocked(maxInstance, expandEnd, instancesTimezone);
14699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            maxInstance = expandEnd;
14709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
14719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Update the bounds on the Instances table.
1473315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        mMetaData.writeLocked(instancesTimezone, minInstance, maxInstance);
14749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
14759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
14769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
14779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public String getType(Uri url) {
14789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int match = sUriMatcher.match(url);
14799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
14809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
14819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event";
14829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
14839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/event";
14849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
14859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/reminder";
14869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
14879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/reminder";
14889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
14899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert";
14909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_BY_INSTANCE:
14919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/calendar-alert-by-instance";
14929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
14939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.item/calendar-alert";
14949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
14959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
14966db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
14979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return "vnd.android.cursor.dir/event-instance";
149848587d3291c4db7f0942e1bff55b88cfa7764ba0Erik            case TIME:
149948587d3291c4db7f0942e1bff55b88cfa7764ba0Erik                return "time/epoch";
1500315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
1501315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return "vnd.android.cursor.dir/property";
15029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
15039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + url);
15049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
15059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1507b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    /**
1508b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     * Determines if the event is recurrent, based on the provided values.
1509b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden     */
1510b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden    public static boolean isRecurrenceEvent(String rrule, String rdate, String originalId,
1511b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            String originalSyncId) {
1512b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden        return (!TextUtils.isEmpty(rrule) ||
1513b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(rdate) ||
1514b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalId) ||
1515b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                !TextUtils.isEmpty(originalSyncId));
15169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
15179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
1518646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    /**
1519646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * Takes an event and corrects the hrs, mins, secs if it is an allDay event.
1520d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
1521646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * AllDay events should have hrs, mins, secs set to zero. This checks if this is true and
1522d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * corrects the fields DTSTART, DTEND, and DURATION if necessary.
1523646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     *
1524d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values The values to check and correct
1525d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param modValues Any updates will be stored here.  This may be the same object as
1526d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   <strong>values</strong>.
1527646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     * @return Returns true if a correction was necessary, false otherwise
1528646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik     */
1529d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean fixAllDayTime(ContentValues values, ContentValues modValues) {
1530499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        Integer allDayObj = values.getAsInteger(Events.ALL_DAY);
1531499287f0ccd3f20f8cf5f9007a9b422b825a7b7cAndy McFadden        if (allDayObj == null || allDayObj == 0) {
1532d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            return false;
1533d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1534d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1535646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        boolean neededCorrection = false;
1536646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1537d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtstart = values.getAsLong(Events.DTSTART);
1538d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Long dtend = values.getAsLong(Events.DTEND);
1539d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String duration = values.getAsString(Events.DURATION);
1540d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        Time time = new Time();
1541d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        String tempValue;
1542d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1543d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // Change dtstart so h,m,s are 0 if necessary.
1544d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.clear(Time.TIMEZONE_UTC);
1545d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        time.set(dtstart.longValue());
1546d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1547d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.hour = 0;
1548d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.minute = 0;
1549d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.second = 0;
1550d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            modValues.put(Events.DTSTART, time.toMillis(true));
1551d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            neededCorrection = true;
1552d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1553d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1554d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // If dtend exists for this event make sure it's h,m,s are 0.
1555d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (dtend != null) {
1556646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            time.clear(Time.TIMEZONE_UTC);
1557d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            time.set(dtend.longValue());
1558646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            if (time.hour != 0 || time.minute != 0 || time.second != 0) {
1559646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.hour = 0;
1560646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.minute = 0;
1561646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                time.second = 0;
1562d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                dtend = time.toMillis(true);
1563d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DTEND, dtend);
1564646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                neededCorrection = true;
1565646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1566d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
1567646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1568d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (duration != null) {
1569d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            int len = duration.length();
1570d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            /* duration is stored as either "P<seconds>S" or "P<days>D". This checks if it's
1571d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             * in the seconds format, and if so converts it to days.
1572d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden             */
1573d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (len == 0) {
1574d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = null;
1575d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else if (duration.charAt(0) == 'P' &&
1576d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    duration.charAt(len - 1) == 'S') {
1577d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int seconds = Integer.parseInt(duration.substring(1, len - 1));
1578d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1579d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                duration = "P" + days + "D";
1580d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DURATION, duration);
1581d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                neededCorrection = true;
1582646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik            }
1583646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        }
1584d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
1585646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik        return neededCorrection;
1586646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik    }
1587646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik
1588bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1589bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1590bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Determines whether the strings in the set name columns that may be overridden
1591bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * when creating a recurring event exception.
1592bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1593bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This uses a white list because it screens out unknown columns and is a bit safer to
1594bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * maintain than a black list.
1595bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1596bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private void checkAllowedInException(Set<String> keys) {
1597bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        for (String str : keys) {
1598bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!ALLOWED_IN_EXCEPTION.contains(str.intern())) {
1599bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                throw new IllegalArgumentException("Exceptions can't overwrite " + str);
1600bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1601bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1602bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1603bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1604bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
160532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * Splits a recurrent event at a specified instance.  This is useful when modifying "this
160632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * and all future events".
160732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
160832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence rule has a COUNT specified, we need to split that at the point of the
160932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * exception.  If the exception is instance N (0-based), the original COUNT is reduced
161032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * to N, and the exception's COUNT is set to (COUNT - N).
161132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
161232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * If the recurrence doesn't have a COUNT, we need to update or introduce an UNTIL value,
161332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * so that the original recurrence will end just before the exception instance.  (Note
161432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * that UNTIL dates are inclusive.)
161532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *<p>
161632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * This should not be used to update the first instance ("update all events" action).
1617bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
161832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @param values The original event values; must include EVENT_TIMEZONE and DTSTART.
161932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        The RRULE value may be modified (with the expectation that this will propagate
162032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     *        into the exception event).
1621bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param endTimeMillis The time before which the event must end (i.e. the start time of the
1622bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *        exception event instance).
162332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden     * @return Values to apply to the original event.
1624bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1625bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static ContentValues setRecurrenceEnd(ContentValues values, long endTimeMillis) {
162632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        boolean origAllDay = values.getAsBoolean(Events.ALL_DAY);
162732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        String origRrule = values.getAsString(Events.RRULE);
1628bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
162932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        EventRecurrence origRecurrence = new EventRecurrence();
163032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        origRecurrence.parse(origRrule);
1631bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
163232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        // Get the start time of the first instance in the original recurrence.
163332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        long startTimeMillis = values.getAsLong(Events.DTSTART);
1634bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Time dtstart = new Time();
1635bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        dtstart.timezone = values.getAsString(Events.EVENT_TIMEZONE);
163632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        dtstart.set(startTimeMillis);
1637bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1638bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        ContentValues updateValues = new ContentValues();
163932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
164032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        if (origRecurrence.count > 0) {
164132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            /*
164232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * Generate the full set of instances for this recurrence, from the first to the
164332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * one just before endTimeMillis.  The list should never be empty, because this method
164432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * should not be called for the first instance.  All we're really interested in is
164532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             * the *number* of instances found.
164632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden             */
164732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceSet recurSet = new RecurrenceSet(values);
164832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            RecurrenceProcessor recurProc = new RecurrenceProcessor();
164932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            long[] recurrences;
165032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            try {
165132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                recurrences = recurProc.expand(dtstart, recurSet, startTimeMillis, endTimeMillis);
165232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            } catch (DateException de) {
165332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException(de);
165432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
165532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
165632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (recurrences.length == 0) {
165732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                throw new RuntimeException("can't use this method on first instance");
165832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
165932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
166032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            EventRecurrence excepRecurrence = new EventRecurrence();
16611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            excepRecurrence.parse(origRrule); // TODO: add/use a copy constructor to EventRecurrence
166232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            excepRecurrence.count -= recurrences.length;
166332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            values.put(Events.RRULE, excepRecurrence.toString());
166432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
166532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.count = recurrences.length;
166632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
166732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        } else {
166832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            Time untilTime = new Time();
166932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
167032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // The "until" time must be in UTC time in order for Google calendar
167132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // to display it properly. For all-day events, the "until" time string
167232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // must include just the date field, and not the time field. The
167332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // repeating events repeat up to and including the "until" time.
167432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.timezone = Time.TIMEZONE_UTC;
167532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
167632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            // Subtract one second from the exception begin time to get the "until" time.
167732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            untilTime.set(endTimeMillis - 1000); // subtract one second (1000 millis)
167832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            if (origAllDay) {
167932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.hour = untilTime.minute = untilTime.second = 0;
168032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.allDay = true;
168132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                untilTime.normalize(false);
168232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
168332aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // This should no longer be necessary -- DTSTART should already be in the correct
168432aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                // format for an all-day event.
168532aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.hour = dtstart.minute = dtstart.second = 0;
168632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.allDay = true;
168732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                dtstart.timezone = Time.TIMEZONE_UTC;
168832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            }
168932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden            origRecurrence.until = untilTime.format2445();
169032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        }
169132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden
169232aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden        updateValues.put(Events.RRULE, origRecurrence.toString());
1693bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        updateValues.put(Events.DTSTART, dtstart.normalize(true));
1694bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        return updateValues;
1695bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
1696bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1697bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    /**
1698bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * Handles insertion of an exception to a recurring event.
1699bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <p>
1700bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * There are two modes, selected based on the presence of "rrule" in modValues:
1701bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <ol>
1702bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Create a single instance exception ("modify current event only").
1703bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * <li> Cap the original event, and create a new recurring event ("modify this and all
1704bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * future events").
1705bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * </ol>
1706bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * This may be used for "modify all instances of the event" by simply selecting the
1707bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * very first instance as the exception target.  In that case, the ID of the "new"
1708bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * exception event will be the same as the originalEventId.
1709bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     *
1710bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param originalEventId The _id of the event to be modified
1711bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @param modValues Event columns to update
1712c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden     * @param callerIsSyncAdapter Set if the content provider client is the sync adapter
1713bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     * @return the ID of the new "exception" event, or -1 on failure
1714bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden     */
1715c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden    private long handleInsertException(long originalEventId, ContentValues modValues,
1716c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            boolean callerIsSyncAdapter) {
1717bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (DEBUG_EXCEPTION) {
1718bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            Log.i(TAG, "RE: values: " + modValues.toString());
1719bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1720bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1721bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Make sure they have specified an instance via originalInstanceTime.
1722bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Long originalInstanceTime = modValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1723bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        if (originalInstanceTime == null) {
1724bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            throw new IllegalArgumentException("Exceptions must specify " +
1725bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Events.ORIGINAL_INSTANCE_TIME);
1726bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
1727bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1728bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Check for attempts to override values that shouldn't be touched.
1729bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        checkAllowedInException(modValues.keySet());
1730bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1731c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        // If this isn't the sync adapter, set the "dirty" flag in any Event we modify.
1732c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        if (!callerIsSyncAdapter) {
1733c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            modValues.put(Events.DIRTY, true);
17347a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            addMutator(modValues, Events.MUTATORS);
1735c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden        }
1736c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1737bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        // Wrap all database accesses in a transaction.
1738bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        mDb.beginTransaction();
1739bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        Cursor cursor = null;
1740bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        try {
1741bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: verify that there's an instance corresponding to the specified time
1742bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (does this matter? it's weird, but not fatal?)
1743bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1744bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Grab the full set of columns for this event.
1745bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor = mDb.query(Tables.EVENTS, null /* columns */,
1746bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    SQL_WHERE_ID, new String[] { String.valueOf(originalEventId) },
1747bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    null /* groupBy */, null /* having */, null /* sortOrder */);
1748bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor.getCount() != 1) {
1749bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event ID " + originalEventId + " lookup failed (count is " +
1750bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        cursor.getCount() + ")");
1751bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1752bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1753bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //DatabaseUtils.dumpCursor(cursor);
1754bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
17552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If there's a color index check that it's valid
1756387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_index = modValues.getAsString(Events.EVENT_COLOR_KEY);
17572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_index)) {
17582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                int calIdCol = cursor.getColumnIndex(Events.CALENDAR_ID);
17592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calId = cursor.getLong(calIdCol);
17602f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
17612f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
17622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calId != null) {
17632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calId);
17642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
17652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
17662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
17672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
17682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
17692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_index, Colors.TYPE_EVENT);
17702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
17712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
1772bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            /*
1773bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * Verify that the original event is in fact a recurring event by checking for the
1774bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * presence of an RRULE.  If it's there, we assume that the event is otherwise
1775bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             * properly constructed (e.g. no DTEND).
1776bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden             */
1777bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            cursor.moveToFirst();
1778bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int rruleCol = cursor.getColumnIndex(Events.RRULE);
1779bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (TextUtils.isEmpty(cursor.getString(rruleCol))) {
1780bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event has no rrule");
1781bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1782bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1783bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (DEBUG_EXCEPTION) {
1784bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.d(TAG, "RE: old RRULE is " + cursor.getString(rruleCol));
1785bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1786bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1787bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Verify that the original event is not itself a (single-instance) exception.
1788bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            int originalIdCol = cursor.getColumnIndex(Events.ORIGINAL_ID);
1789bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (!TextUtils.isEmpty(cursor.getString(originalIdCol))) {
1790bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Log.e(TAG, "Original event is an exception");
1791bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return -1;
1792bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
1793bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1794bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createSingleException = TextUtils.isEmpty(modValues.getAsString(Events.RRULE));
1795bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1796bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // TODO: check for the presence of an existing exception on this event+instance?
1797bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       The caller should be modifying that, not creating another exception.
1798bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            //       (Alternatively, we could do that for them.)
1799bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1800bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // Create a new ContentValues for the new event.  Start with the original event,
1801bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            // and drop in the new caller-supplied values.  This will set originalInstanceTime.
1802bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            ContentValues values = new ContentValues();
1803bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
1804f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor.close();
1805f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden            cursor = null;
1806bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1807b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            // TODO: if we're changing this to an all-day event, we should ensure that
1808b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       hours/mins/secs on DTSTART are zeroed out (before computing DTEND).
1809b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden            //       See fixAllDayTime().
1810b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1811bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            boolean createNewEvent = true;
1812bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createSingleException) {
1813bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1814bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Save a copy of a few fields that will migrate to new places.
1815bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1816bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _id = values.getAsString(Events._ID);
1817bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String _sync_id = values.getAsString(Events._SYNC_ID);
1818bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean allDay = values.getAsBoolean(Events.ALL_DAY);
1819bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1820bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1821bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Wipe out some fields that we don't want to clone into the exception event.
1822bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1823bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                for (String str : DONT_CLONE_INTO_EXCEPTION) {
1824bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(str);
1825bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1826bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1827bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1828bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Merge the new values on top of the existing values.  Note this sets
1829bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * originalInstanceTime.
1830bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1831bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.putAll(modValues);
1832bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1833bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1834bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Copy some fields to their "original" counterparts:
1835bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _id --> original_id
1836bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   _sync_id --> original_sync_id
1837bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *   allDay --> originalAllDay
1838bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1839bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this event hasn't been sync'ed with the server yet, the _sync_id field will
1840bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * be null.  We will need to fill original_sync_id in later.  (May not be able to
1841bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * do it right when our own _sync_id field gets populated, because the order of
1842bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * events from the server may not be what we want -- could update the exception
1843bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * before updating the original event.)
1844bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1845bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * _id is removed later (right before we write the event).
1846bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1847bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ID, _id);
1848bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_SYNC_ID, _sync_id);
1849bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.ORIGINAL_ALL_DAY, allDay);
1850bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1851bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // Mark the exception event status as "tentative", unless the caller has some
1852bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // other value in mind (like STATUS_CANCELED).
1853bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (!values.containsKey(Events.STATUS)) {
1854bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.put(Events.STATUS, Events.STATUS_TENTATIVE);
1855bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1856bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1857c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                // We're converting from recurring to non-recurring.
1858c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                // Clear out RRULE, RDATE, EXRULE & EXDATE
1859c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                // Replace DURATION with DTEND.
1860c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                values.remove(Events.RRULE);
1861c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                values.remove(Events.RDATE);
1862c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                values.remove(Events.EXRULE);
1863c817154d2185340a0d0d4b81f06e33e32ce81b37Alon Albert                values.remove(Events.EXDATE);
1864bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1865bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                Duration duration = new Duration();
1866bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                String durationStr = values.getAsString(Events.DURATION);
1867bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                try {
1868bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    duration.parse(durationStr);
1869bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } catch (Exception ex) {
1870bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // NullPointerException if the original event had no duration.
1871bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // DateException if the duration was malformed.
1872bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Bad duration in recurring event: " + durationStr, ex);
1873bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1874bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1875bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1876c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1877c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * We want to compute DTEND as an offset from the start time of the instance.
1878c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If the caller specified a new value for DTSTART, we want to use that; if not,
1879c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the DTSTART in "values" will be the start time of the first instance in the
1880c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * recurrence, so we want to replace it with ORIGINAL_INSTANCE_TIME.
1881c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
1882c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                long start;
1883c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.DTSTART)) {
1884c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.DTSTART);
1885c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                } else {
1886c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    start = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
1887c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    values.put(Events.DTSTART, start);
1888c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
1889bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.put(Events.DTEND, start + duration.getMillis());
1890bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1891c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    Log.d(TAG, "RE: ORIG_INST_TIME=" + start +
1892c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            ", duration=" + duration.getMillis() +
1893bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            ", generated DTEND=" + values.getAsLong(Events.DTEND));
1894bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
189585c09a31bcc3a18e173428bf7b628cec2834bebcAndy McFadden                values.remove(Events.DURATION);
1896bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
1897bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1898bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * We're going to "split" the recurring event, making the old one stop before
1899bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * this instance, and creating a new recurring event that starts here.
1900bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1901bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * No need to fill out the "original" fields -- the new event is not tied to
1902bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * the previous event in any way.
1903bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 *
1904bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * If this is the first event in the series, we can just update the existing
1905bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * event with the values.
1906bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1907bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                boolean canceling = (values.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
1908bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1909bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (originalInstanceTime.equals(values.getAsLong(Events.DTSTART))) {
1910bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
1911bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * Update fields in the existing event.  Rather than use the merged data
1912bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * from the cursor, we just do the update with the new value set after
1913bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * removing the ORIGINAL_INSTANCE_TIME entry.
1914bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1915bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (canceling) {
1916bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        // TODO: should we just call deleteEventInternal?
1917bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "Note: canceling entire event via exception call");
1918bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1919bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1920bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: updating full event");
1921bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1922ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    if (!validateRecurrenceRule(modValues)) {
1923ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        throw new IllegalArgumentException("Invalid recurrence rule: " +
1924ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                                values.getAsString(Events.RRULE));
1925ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                    }
1926bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    modValues.remove(Events.ORIGINAL_INSTANCE_TIME);
1927bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
1928bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1929bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    createNewEvent = false; // skip event creation and related-table cloning
1930bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                } else {
1931bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    if (DEBUG_EXCEPTION) {
1932bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                        Log.d(TAG, "RE: splitting event");
1933bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    }
1934bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1935bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
193632aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Cap the original event so it ends just before the target instance.  In
193732aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * some cases (nonzero COUNT) this will also update the RRULE in "values",
193832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * so that the exception we're creating terminates appropriately.  If a
193932aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * new RRULE was specified by the caller, the new rule will overwrite our
194032aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * changes when we merge the new values in below (which is the desired
194132aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * behavior).
1942bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1943bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    ContentValues splitValues = setRecurrenceEnd(values, originalInstanceTime);
1944bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    mDb.update(Tables.EVENTS, splitValues, SQL_WHERE_ID,
1945bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                            new String[] { Long.toString(originalEventId) });
1946bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1947bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    /*
194832aa776d04075be5b5c945c68f7f352f4a3038b7Andy McFadden                     * Prepare the new event.  We remove originalInstanceTime, because we're now
1949bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * creating a new event rather than an exception.
1950bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     *
1951bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * We're always cloning a non-exception event (we tested to make sure the
1952bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * event doesn't specify original_id, and we don't allow original_id in the
1953bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * modValues), so we shouldn't end up creating a new event that looks like
1954bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     * an exception.
1955bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                     */
1956bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.putAll(modValues);
1957bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    values.remove(Events.ORIGINAL_INSTANCE_TIME);
1958bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1959c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden            }
1960bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1961bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            long newEventId;
1962bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (createNewEvent) {
1963bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                values.remove(Events._ID);      // don't try to set this explicitly
1964be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
1965be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(values, null);
1966be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
1967be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(values);
1968be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
1969bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1970bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = mDb.insert(Tables.EVENTS, null, values);
1971bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (newEventId < 0) {
1972bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Unable to add exception to recurring event");
1973bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.w(TAG, "Values: " + values);
1974bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    return -1;
1975bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1976bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                if (DEBUG_EXCEPTION) {
1977bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    Log.d(TAG, "RE: new ID is " + newEventId);
1978bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                }
1979bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
1980b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                // TODO: do we need to do something like this?
1981b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                //updateEventRawTimesLocked(id, updatedValues);
1982b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1983b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
1984b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Force re-computation of the Instances associated with the recurrence event.
1985b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
1986b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, newEventId, true, mDb);
1987b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
1988bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                /*
1989bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 * Some of the other tables (Attendees, Reminders, ExtendedProperties) reference
1990c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * the Event ID.  We need to copy the entries from the old event, filling in the
1991c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * new event ID, so that somebody doing a SELECT on those tables will find
1992c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * matching entries.
1993bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                 */
1994bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                CalendarDatabaseHelper.copyEventRelatedTables(mDb, newEventId, originalEventId);
1995c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
1996c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                /*
1997c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * If we modified Event.selfAttendeeStatus, we need to keep the corresponding
1998c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 * entry in the Attendees table in sync.
1999c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                 */
2000c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
2001c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    /*
2002c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * Each Attendee is identified by email address.  To find the entry that
2003c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * corresponds to "self", we want to compare that address to the owner of
2004c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     * the Calendar.  We're expecting to find one matching entry in Attendees.
2005c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                     */
2006c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    long calendarId = values.getAsLong(Events.CALENDAR_ID);
2007f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    String accountName = getOwner(calendarId);
2008f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden
2009f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden                    if (accountName != null) {
2010c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        ContentValues attValues = new ContentValues();
2011c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        attValues.put(Attendees.ATTENDEE_STATUS,
2012c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                modValues.getAsString(Events.SELF_ATTENDEE_STATUS));
2013c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden
2014c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        if (DEBUG_EXCEPTION) {
2015c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                            Log.d(TAG, "Updating attendee status for event=" + newEventId +
2016c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    " name=" + accountName + " to " +
2017c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                    attValues.getAsString(Attendees.ATTENDEE_STATUS));
2018c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
2019c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        int count = mDb.update(Tables.ATTENDEES, attValues,
2020c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
2021c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                                new String[] { String.valueOf(newEventId), accountName });
2022b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                        if (count != 1 && count != 2) {
2023b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // We're only expecting one matching entry.  We might briefly see
2024b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            // two during a server sync.
20257148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                            Log.e(TAG, "Attendee status update on event=" + newEventId
20267148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                    + " touched " + count + " rows. Expected one or two rows.");
2027b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            if (false) {
2028b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                // This dumps PII in the log, don't ship with it enabled.
2029b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                Cursor debugCursor = mDb.query(Tables.ATTENDEES, null,
2030b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        Attendees.EVENT_ID + "=? AND " +
2031b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                            Attendees.ATTENDEE_EMAIL + "=?",
2032b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        new String[] { String.valueOf(newEventId), accountName },
2033b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                        null, null, null);
2034b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                                DatabaseUtils.dumpCursor(debugCursor);
20350332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                if (debugCursor != null) {
20360332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                    debugCursor.close();
20370332925aa9db8c4826327edd85030a4791b7a8e6Michael Chan                                }
2038b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            }
2039b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                            throw new RuntimeException("Status update WTF");
2040c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                        }
2041c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                    }
2042c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                }
2043bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            } else {
2044b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                /*
2045b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 * Update any Instances changed by the update to this Event.
2046b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                 */
2047b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                mInstancesHelper.updateInstancesLocked(values, originalEventId, false, mDb);
2048bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                newEventId = originalEventId;
2049bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
2050bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
2051bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.setTransactionSuccessful();
2052bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            return newEventId;
2053bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        } finally {
2054bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            if (cursor != null) {
2055bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                cursor.close();
2056bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
2057bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            mDb.endTransaction();
2058bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden        }
2059bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    }
2060bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden
2061222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    /**
2062222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Fills in the originalId column for previously-created exceptions to this event.  If
2063222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this event is not recurring or does not have a _sync_id, this does nothing.
2064222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2065222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * The server might send exceptions before the event they refer to.  When
2066222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * this happens, the originalId field will not have been set in the
2067222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * exception events (it's the recurrence events' _id field, so it can't be
2068222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * known until the recurrence event is created).  When we add a recurrence
2069222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * event with a non-empty _sync_id field, we write that event's _id to the
2070222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * originalId field of any events whose originalSyncId matches _sync_id.
2071222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * <p>
2072222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * Note _sync_id is only expected to be unique within a particular calendar.
2073222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     *
2074222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param id The ID of the Event
2075222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     * @param values Values for the Event being inserted
2076222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden     */
2077222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    private void backfillExceptionOriginalIds(long id, ContentValues values) {
2078222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String syncId = values.getAsString(Events._SYNC_ID);
2079222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2080222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String rdate = values.getAsString(Events.RDATE);
2081222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        String calendarId = values.getAsString(Events.CALENDAR_ID);
2082222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2083222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        if (TextUtils.isEmpty(syncId) || TextUtils.isEmpty(calendarId) ||
2084222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                (TextUtils.isEmpty(rrule) && TextUtils.isEmpty(rdate))) {
2085222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            // Not a recurring event, or doesn't have a server-provided sync ID.
2086222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden            return;
2087222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        }
2088222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2089222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        ContentValues originalValues = new ContentValues();
2090222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        originalValues.put(Events.ORIGINAL_ID, id);
2091222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden        mDb.update(Tables.EVENTS, originalValues,
2092222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                Events.ORIGINAL_SYNC_ID + "=? AND " + Events.CALENDAR_ID + "=?",
2093222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                new String[] { syncId, calendarId });
2094222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden    }
2095222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
20969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
2097b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
2098ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
20999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "insertInTransaction: " + uri);
21009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
21018d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
21020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
21030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_INSERT, uri, values, callerIsSyncAdapter, match,
21040739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                null /* selection */, null /* selection args */);
21050ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
21069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long id = 0;
21089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
21099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
2110bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case SYNCSTATE:
21119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.getSyncState().insert(mDb, values);
21129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
21139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS:
21147e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2115c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
21167a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    addMutator(values, Events.MUTATORS);
21177e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
21189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Events.DTSTART)) {
21198253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    if (values.containsKey(Events.ORIGINAL_SYNC_ID)
21208253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                            && values.containsKey(Events.ORIGINAL_INSTANCE_TIME)
21218253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                            && Events.STATUS_CANCELED == values.getAsInteger(Events.STATUS)) {
21228253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        // event is a canceled instance of a recurring event, it doesn't these
21238253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        // values but lets fake some to satisfy curious consumers.
21248253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        final long origStart = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
21258253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.DTSTART, origStart);
21268253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.DTEND, origStart);
21278253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        values.put(Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC);
21288253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    } else {
21298253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                        throw new RuntimeException("DTSTART field missing from event");
21308253a84ce7abf2fa1c662b735432a502f4ace96fAlon Albert                    }
21319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // TODO: do we really need to make a copy?
2133e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                ContentValues updatedValues = new ContentValues(values);
2134be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                if (callerIsSyncAdapter) {
2135be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    scrubEventData(updatedValues, null);
2136be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                } else {
2137be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                    validateEventData(updatedValues);
2138be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                }
2139e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                // updateLastDate must be after validation, to ensure proper last date computation
2140e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                updatedValues = updateLastDate(updatedValues);
21419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (updatedValues == null) {
21429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new RuntimeException("Could not insert event.");
21439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // return null;
21449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
21452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Long calendar_id = updatedValues.getAsLong(Events.CALENDAR_ID);
21462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (calendar_id == null) {
21472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // validateEventData checks this for non-sync adapter
21482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    // inserts
21492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("New events must specify a calendar id");
21502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color is valid if it is being set
2152387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = updatedValues.getAsString(Events.EVENT_COLOR_KEY);
21532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
21542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Account account = getAccount(calendar_id);
21552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = null;
21562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = null;
21572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (account != null) {
21582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = account.name;
21592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = account.type;
21602f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
21612f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, color_id,
21622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_EVENT);
21632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    updatedValues.put(Events.EVENT_COLOR, color);
21642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
21659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String owner = null;
21662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!updatedValues.containsKey(Events.ORGANIZER)) {
21672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    owner = getOwner(calendar_id);
21689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // TODO: This isn't entirely correct.  If a guest is adding a recurrence
21699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // exception to an event, the organizer should stay the original organizer.
21709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // This value doesn't go to the server and it will get fixed on sync,
21719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // so it shouldn't really matter.
21729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (owner != null) {
21739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        updatedValues.put(Events.ORGANIZER, owner);
21749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
21759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
217634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
217734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && !updatedValues.containsKey(Events.ORIGINAL_ID)) {
2178503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    long originalId = getOriginalId(updatedValues
2179503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                            .getAsString(Events.ORIGINAL_SYNC_ID),
2180ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                            updatedValues.getAsString(Events.CALENDAR_ID));
218134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (originalId != -1) {
218234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_ID, originalId);
218334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
218434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                } else if (!updatedValues.containsKey(Events.ORIGINAL_SYNC_ID)
218534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        && updatedValues.containsKey(Events.ORIGINAL_ID)) {
218634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    String originalSyncId = getOriginalSyncId(updatedValues
218734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                            .getAsLong(Events.ORIGINAL_ID));
218834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    if (!TextUtils.isEmpty(originalSyncId)) {
218934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                        updatedValues.put(Events.ORIGINAL_SYNC_ID, originalSyncId);
219034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    }
219134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                }
2192d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (fixAllDayTime(updatedValues, updatedValues)) {
2193f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.WARN)) {
2194f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.w(TAG, "insertInTransaction: " +
2195f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                                "allDay is true but sec, min, hour were not 0.");
2196f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
2197646444fdde3bde0a2ac948e021bc52b07c1d4a18Erik                }
21981c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                updatedValues.remove(Events.HAS_ALARM);     // should not be set by caller
2199c4d44fd028e7f5f44f46439c3410dab3456e6d3fFabrice Di Meglio                // Insert the row
22009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.eventsInsert(updatedValues);
22019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (id != -1) {
22029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    updateEventRawTimesLocked(id, updatedValues);
2203f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                    mInstancesHelper.updateInstancesLocked(updatedValues, id,
2204f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                            true /* new event */, mDb);
22059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
22069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // If we inserted a new event that specified the self-attendee
22079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // status, then we need to add an entry to the attendees table.
22089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    if (values.containsKey(Events.SELF_ATTENDEE_STATUS)) {
22099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        int status = values.getAsInteger(Events.SELF_ATTENDEE_STATUS);
22109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (owner == null) {
22112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            owner = getOwner(calendar_id);
22129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
22139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        createAttendeeEntry(id, status, owner);
22149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
2215b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden
2216222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden                    backfillExceptionOriginalIds(id, values);
2217222f23bb26b6a72a9a0725593f456cfe497f7e91Andy McFadden
2218dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(id, callerIsSyncAdapter);
22199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
22209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2221bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID:
2222bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long originalEventId = ContentUris.parseId(uri);
2223c832113820b3fe514077b45dc4daaae970ef3284Andy McFadden                id = handleInsertException(originalEventId, values, callerIsSyncAdapter);
2224bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                break;
22259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
222682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden                // TODO: verify that all required fields are present
22279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
22289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null && syncEvents == 1) {
2229c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
22309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    String accountType = values.getAsString(
2231c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                            Calendars.ACCOUNT_TYPE);
22329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    final Account account = new Account(accountName, accountType);
2233fa332ecedc0c340109811552407142f6e4f600b2RoboErik                    String eventsUrl = values.getAsString(Calendars.CAL_SYNC1);
22341b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    mDbHelper.scheduleSync(account, false /* two-way sync */, eventsUrl);
22359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2236387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String cal_color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
22372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(cal_color_id)) {
22382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
22392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
22402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = verifyColorExists(accountName, accountType, cal_color_id,
22412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            Colors.TYPE_CALENDAR);
22422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    values.put(Calendars.CALENDAR_COLOR, color);
22432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarsInsert(values);
2245dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                sendUpdateNotification(id, callerIsSyncAdapter);
22469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
22472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
22482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // verifyTransactionAllowed requires this be from a sync
22492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter, all of the required fields are marked NOT NULL in
22502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // the db. TODO Do we need explicit checks here or should we
22512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // just let sqlite throw if something isn't specified?
22522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = uri.getQueryParameter(Colors.ACCOUNT_NAME);
22532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = uri.getQueryParameter(Colors.ACCOUNT_TYPE);
2254387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String colorIndex = values.getAsString(Colors.COLOR_KEY);
22552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
22562f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Account name and type must be non"
22572f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            + " empty parameters for " + uri);
22582f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22592f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (TextUtils.isEmpty(colorIndex)) {
22602f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("COLOR_INDEX must be non empty for " + uri);
22612f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22622f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!values.containsKey(Colors.COLOR_TYPE) || !values.containsKey(Colors.COLOR)) {
22632f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException(
22642f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            "New colors must contain COLOR_TYPE and COLOR");
22652f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Make sure the account we're inserting for is the same one the
22672f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // adapter is claiming to be. TODO should we throw if they
22682f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // aren't the same?
22692f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_NAME, accountName);
22702f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                values.put(Colors.ACCOUNT_TYPE, accountType);
22712f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
22722f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Verify the color doesn't already exist
22732f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = null;
22742f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
22754755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    final long colorType = values.getAsLong(Colors.COLOR_TYPE);
22764755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                    c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
22772f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.getCount() != 0) {
22784755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        throw new IllegalArgumentException("color type " + colorType
22794755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                                + " and index " + colorIndex
22807148c4fbb67fd9b20fb0b92d23e831b05ec22155RoboErik                                + " already exists for account and type provided");
22812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
22822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
22832f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null)
22842f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
22852f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
22862f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                id = mDbHelper.colorsInsert(values);
22872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                break;
2288503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            case ATTENDEES: {
22899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!values.containsKey(Attendees.EVENT_ID)) {
22909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Attendees values must "
22919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            + "contain an event_id");
22929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2293503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
2294503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (!doesEventExist(eventIdObj)) {
2295503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    Log.i(TAG, "Trying to insert a attendee to a non-existent event");
2296503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    return null;
2297503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                }
22987e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
22999ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    final Long eventId = values.getAsLong(Attendees.EVENT_ID);
23009ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
23019ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
23027e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
23039ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.attendeesInsert(values);
23049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Copy the attendee status value to the Events table.
23069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                updateEventAttendeeStatus(mDb, values);
23079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2308503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            }
2309503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            case REMINDERS: {
23101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
23111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventIdObj == null) {
23129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("Reminders values must "
23131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            + "contain a numeric event_id");
23149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2315503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (!doesEventExist(eventIdObj)) {
2316503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    Log.i(TAG, "Trying to insert a reminder to a non-existent event");
2317503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    return null;
2318503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                }
2319503a798e5f76ecce75607277292bd9a326ba79ecTony Mak
23207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
23211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventIdObj);
23221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    setEventDirty(eventIdObj);
23237e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
23249ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.remindersInsert(values);
23259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // We know this event has at least one reminder, so make sure "hasAlarm" is 1.
23271c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                setHasAlarm(eventIdObj, 1);
23281c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
23299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Schedule another event alarm, if necessary
23309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
23319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "insertInternal() changing reminder");
23329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
2333ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
23349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
23351c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
2336503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            case CALENDAR_ALERTS: {
2337503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
2338503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (eventIdObj == null) {
23399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("CalendarAlerts values must "
2340503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                            + "contain a numeric event_id");
2341503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                }
2342503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (!doesEventExist(eventIdObj)) {
2343503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    Log.i(TAG, "Trying to insert an alert to a non-existent event");
2344503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    return null;
23459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                id = mDbHelper.calendarAlertsInsert(values);
23472fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
23482fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
23499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2350503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            }
2351503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            case EXTENDED_PROPERTIES: {
2352503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                Long eventIdObj = values.getAsLong(Reminders.EVENT_ID);
2353503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (eventIdObj == null) {
23549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    throw new IllegalArgumentException("ExtendedProperties values must "
2355503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                            + "contain a numeric event_id");
2356503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                }
2357503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                if (!doesEventExist(eventIdObj)) {
2358503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    Log.i(TAG, "Trying to insert extended properties to a non-existent event id = "
2359503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                            + eventIdObj);
2360503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                    return null;
23619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
23627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (!callerIsSyncAdapter) {
2363b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    final Long eventId = values
2364b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            .getAsLong(CalendarContract.ExtendedProperties.EVENT_ID);
23659ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    mDbHelper.duplicateEvent(eventId);
23669ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    setEventDirty(eventId);
23677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
23689ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                id = mDbHelper.extendedPropertiesInsert(values);
23699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                break;
2370503a798e5f76ecce75607277292bd9a326ba79ecTony Mak            }
23713b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            case EMMA:
23723b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                // Special target used during code-coverage evaluation.
23733b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                handleEmmaRequest(values);
23743b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                break;
23759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
23769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
23779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
23789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EXTENDED_PROPERTIES_ID:
23799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
23809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
23816db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
2382315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
23837e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                throw new UnsupportedOperationException("Cannot insert into that URL: " + uri);
23849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
23859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
23869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
23889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (id < 0) {
23899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
23909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
23919f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return ContentUris.withAppendedId(uri, id);
23929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
23939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2394503a798e5f76ecce75607277292bd9a326ba79ecTony Mak    private boolean doesEventExist(long eventId) {
2395503a798e5f76ecce75607277292bd9a326ba79ecTony Mak        return DatabaseUtils.queryNumEntries(mDb, Tables.EVENTS, Events._ID + "=?",
2396503a798e5f76ecce75607277292bd9a326ba79ecTony Mak                new String[]{String.valueOf(eventId)}) > 0;
2397503a798e5f76ecce75607277292bd9a326ba79ecTony Mak    }
2398503a798e5f76ecce75607277292bd9a326ba79ecTony Mak
2399e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
24003b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * Handles special commands related to EMMA code-coverage testing.
24013b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     *
24023b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     * @param values Parameters from the caller.
24033b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden     */
24043b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static void handleEmmaRequest(ContentValues values) {
24053b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        /*
24063b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * This is not part of the public API, so we can't share constants with the CTS
24073b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * test code.
24083b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         *
24093b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * Bad requests, or attempting to request EMMA coverage data when the coverage libs
24103b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         * aren't linked in, will cause an exception.
24113b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden         */
24123b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        String cmd = values.getAsString("cmd");
24133b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        if (cmd.equals("start")) {
24143b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // We'd like to reset the coverage data, but according to FAQ item 3.14 at
24153b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // http://emma.sourceforge.net/faq.html, this isn't possible in 2.0.
24163b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            Log.d(TAG, "Emma coverage testing started");
24173b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        } else if (cmd.equals("stop")) {
24183b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // Call com.vladium.emma.rt.RT.dumpCoverageData() to cause a data dump.  We
24193b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            // may not have been built with EMMA, so we need to do this through reflection.
24203b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            String filename = values.getAsString("outputFileName");
24213b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
24223b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            File coverageFile = new File(filename);
24233b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            try {
24243b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Class<?> emmaRTClass = Class.forName("com.vladium.emma.rt.RT");
24253b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData",
24263b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        coverageFile.getClass(), boolean.class, boolean.class);
24273b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
24283b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                dumpCoverageMethod.invoke(null, coverageFile, false /*merge*/,
24293b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                        false /*stopDataCollection*/);
24303b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                Log.d(TAG, "Emma coverage data written to " + filename);
24313b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            } catch (Exception e) {
24323b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden                throw new RuntimeException("Emma coverage dump failed", e);
24333b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden            }
24343b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        }
24353b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    }
24363b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden
24373b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    /**
24385ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * Validates the recurrence rule, if any.  We allow single- and multi-rule RRULEs.
2439ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * <p>
24405ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * TODO: Validate RDATE, EXRULE, EXDATE (possibly passing in an indication of whether we
24415ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden     * believe we have the full set, so we can reject EXRULE when not accompanied by RRULE).
2442ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     *
2443ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     * @return A boolean indicating successful validation.
2444ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden     */
2445ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    private boolean validateRecurrenceRule(ContentValues values) {
2446ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        String rrule = values.getAsString(Events.RRULE);
2447ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2448ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        if (!TextUtils.isEmpty(rrule)) {
24495ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            String[] ruleList = rrule.split("\n");
24505ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden            for (String recur : ruleList) {
24515ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                EventRecurrence er = new EventRecurrence();
24525ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                try {
24535ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    er.parse(recur);
24545ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                } catch (EventRecurrence.InvalidFormatException ife) {
24555ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    Log.w(TAG, "Invalid recurrence rule: " + recur);
2456bfea6da707f8d352432096371e7da76c230d9059Michael Chan                    dumpEventNoPII(values);
24575ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                    return false;
24585ce2a67ba623d3a32a2aa3bb70c5ded7e8fd7b5bAndy McFadden                }
2459ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2460ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        }
2461ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2462ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden        return true;
2463ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    }
2464ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden
2465bfea6da707f8d352432096371e7da76c230d9059Michael Chan    private void dumpEventNoPII(ContentValues values) {
2466bfea6da707f8d352432096371e7da76c230d9059Michael Chan        if (values == null) {
2467bfea6da707f8d352432096371e7da76c230d9059Michael Chan            return;
2468bfea6da707f8d352432096371e7da76c230d9059Michael Chan        }
2469bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2470bfea6da707f8d352432096371e7da76c230d9059Michael Chan        StringBuilder bob = new StringBuilder();
2471bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("dtStart:       ").append(values.getAsLong(Events.DTSTART));
2472bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndtEnd:         ").append(values.getAsLong(Events.DTEND));
2473bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nall_day:       ").append(values.getAsInteger(Events.ALL_DAY));
2474bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ntz:            ").append(values.getAsString(Events.EVENT_TIMEZONE));
2475bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\ndur:           ").append(values.getAsString(Events.DURATION));
2476bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrrule:         ").append(values.getAsString(Events.RRULE));
2477bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nrdate:         ").append(values.getAsString(Events.RDATE));
2478bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nlast_date:     ").append(values.getAsLong(Events.LAST_DATE));
2479bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2480bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nid:            ").append(values.getAsLong(Events._ID));
2481bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nsync_id:       ").append(values.getAsString(Events._SYNC_ID));
2482bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_id:        ").append(values.getAsLong(Events.ORIGINAL_ID));
2483bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_sync_id:   ").append(values.getAsString(Events.ORIGINAL_SYNC_ID));
2484bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_inst_time: ").append(values.getAsLong(Events.ORIGINAL_INSTANCE_TIME));
2485bfea6da707f8d352432096371e7da76c230d9059Michael Chan        bob.append("\nori_all_day:   ").append(values.getAsInteger(Events.ORIGINAL_ALL_DAY));
2486bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2487bfea6da707f8d352432096371e7da76c230d9059Michael Chan        Log.i(TAG, bob.toString());
2488bfea6da707f8d352432096371e7da76c230d9059Michael Chan    }
2489bfea6da707f8d352432096371e7da76c230d9059Michael Chan
2490ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden    /**
249162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Do some scrubbing on event data before inserting or updating. In particular make
249262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * dtend, duration, etc make sense for the type of event (regular, recurrence, exception).
249362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * Remove any unexpected fields.
2494e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     *
249562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param values the ContentValues to insert.
249662fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     * @param modValues if non-null, explicit null entries will be added here whenever something
249762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden     *   is removed from <strong>values</strong>.
2498e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
249962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden    private void scrubEventData(ContentValues values, ContentValues modValues) {
2500e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2501e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2502e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2503e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2504c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        boolean hasOriginalEvent = !TextUtils.isEmpty(values.getAsString(Events.ORIGINAL_SYNC_ID));
2505e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        boolean hasOriginalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME) != null;
2506e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        if (hasRrule || hasRdate) {
2507e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence:
2508e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of first event
2509e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is null
2510e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is the duration of the event
2511ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            // rrule is a valid recurrence rule
2512e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the end of the last event or null if it repeats forever
2513e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2514e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2515ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            if (!validateRecurrenceRule(values)) {
2516ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2517ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden                        values.getAsString(Events.RRULE));
2518ba54f5f9ca0c33fd518b1c87bb15fb7907672e04Andy McFadden            }
2519e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (hasDtend || !hasDuration || hasOriginalEvent || hasOriginalInstanceTime) {
252062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DTEND, ORIGINAL_SYNC_ID, ORIGINAL_INSTANCE_TIME");
2521e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
252262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence: " + values);
2523e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2524e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DTEND);
2525c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                values.remove(Events.ORIGINAL_SYNC_ID);
2526e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.ORIGINAL_INSTANCE_TIME);
252762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
252862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DTEND);
252962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_SYNC_ID);
253062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.ORIGINAL_INSTANCE_TIME);
253162fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2532e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2533e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else if (hasOriginalEvent || hasOriginalInstanceTime) {
2534e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Recurrence exception
2535e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is start time of exception event
2536e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is end time of exception event
2537e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2538e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2539e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastdate is same as dtend
2540e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is the _sync_id of the recurrence
2541e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is the start time of the event being replaced
2542e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration || !hasOriginalEvent || !hasOriginalInstanceTime) {
254362fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2544e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
254562fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for recurrence exception: " + values);
2546e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2547e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
254862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
254962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
255062fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2551e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2552e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        } else {
2553e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // Regular event
2554e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtstart is the start time
2555e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // dtend is the end time
2556e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // duration is null
2557e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // rrule is null
2558e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // lastDate is the same as dtend
2559e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalEvent is null
2560e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            // originalInstanceTime is null
2561e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            if (!hasDtend || hasDuration) {
256262fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                Log.d(TAG, "Scrubbing DURATION");
2563e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
256462fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    Log.d(TAG, "Invalid values for event: " + values);
2565e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                }
2566e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff                values.remove(Events.DURATION);
256762fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                if (modValues != null) {
256862fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                    modValues.putNull(Events.DURATION);
256962fb6911ea17d10de9662f455983ea045324aa62Andy McFadden                }
2570e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff            }
2571e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff        }
2572e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    }
2573e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff
2574d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
2575d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Validates event data.  Pass in the full set of values for the event (i.e. not just
2576d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * a part that's being updated).
2577d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2578d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values Event data.
2579d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws IllegalArgumentException if bad data is found.
2580d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
2581d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private void validateEventData(ContentValues values) {
258282b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.CALENDAR_ID))) {
258382b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include a calendar_id");
258482b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
258582b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        if (TextUtils.isEmpty(values.getAsString(Events.EVENT_TIMEZONE))) {
258682b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden            throw new IllegalArgumentException("Event values must include an eventTimezone");
258782b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden        }
258882b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden
2589d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtstart = values.getAsLong(Events.DTSTART) != null;
2590d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDtend = values.getAsLong(Events.DTEND) != null;
2591d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasDuration = !TextUtils.isEmpty(values.getAsString(Events.DURATION));
2592d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRrule = !TextUtils.isEmpty(values.getAsString(Events.RRULE));
2593d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean hasRdate = !TextUtils.isEmpty(values.getAsString(Events.RDATE));
2594d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasRrule || hasRdate) {
2595d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!validateRecurrenceRule(values)) {
2596d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Invalid recurrence rule: " +
2597d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        values.getAsString(Events.RRULE));
2598d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
2599d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2600d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
2601d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDtstart) {
2602bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2603d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTSTART cannot be empty.");
2604d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2605d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (!hasDuration && !hasDtend) {
2606bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2607d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("DTEND and DURATION cannot both be null for " +
2608d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    "an event.");
2609d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2610d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (hasDuration && hasDtend) {
2611bfea6da707f8d352432096371e7da76c230d9059Michael Chan            dumpEventNoPII(values);
2612d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            throw new IllegalArgumentException("Cannot have both DTEND and DURATION in an event");
2613d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
2614d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
2615d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
26169ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private void setEventDirty(long eventId) {
26177a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String mutators = DatabaseUtils.stringForQuery(
26187a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                mDb,
26197a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                SQL_QUERY_EVENT_MUTATORS,
26207a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                new String[]{String.valueOf(eventId)});
26217a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String packageName = getCallingPackageName();
26227a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String newMutators;
26237a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        if (TextUtils.isEmpty(mutators)) {
26247a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            newMutators = packageName;
26257a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        } else  {
26267a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            final String[] strings = mutators.split(",");
26277a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            boolean found = false;
26287a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            for (String string : strings) {
26297a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                if (string.equals(packageName)) {
26307a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    found = true;
26317a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    break;
26327a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                }
26337a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            }
26347a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            if (!found) {
26357a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                newMutators = mutators + "," + packageName;
26367a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            } else {
26377a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                newMutators = mutators;
26387a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            }
26397a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        }
26407a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        mDb.execSQL(SQL_UPDATE_EVENT_SET_DIRTY_AND_MUTATORS,
26417a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                new Object[] {newMutators, eventId});
26427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
26437e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
2644ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson    private long getOriginalId(String originalSyncId, String calendarId) {
2645ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson        if (TextUtils.isEmpty(originalSyncId) || TextUtils.isEmpty(calendarId)) {
264634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return -1;
264734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
264834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
264934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        long originalId = -1;
265034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
265134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
265234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, ID_ONLY_PROJECTION,
2653ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                    Events._SYNC_ID + "=?"  + " AND " + Events.CALENDAR_ID + "=?",
2654ff5d02de9fddecbd5649f243233514e256a705c2Isaac Katzenelson                    new String[] {originalSyncId, calendarId}, null);
265534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
265634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalId = c.getLong(0);
265734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
265834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
265934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
266034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
266134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
266234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
266334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalId;
266434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
266534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
266634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    private String getOriginalSyncId(long originalId) {
266734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        if (originalId == -1) {
266834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            return null;
266934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
267034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        // Get the original id for this event
267134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        String originalSyncId = null;
267234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        Cursor c = null;
267334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        try {
267434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            c = query(Events.CONTENT_URI, new String[] {Events._SYNC_ID},
267534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                    Events._ID + "=?", new String[] {Long.toString(originalId)}, null);
267634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null && c.moveToFirst()) {
267734c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                originalSyncId = c.getString(0);
267834c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
267934c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        } finally {
268034c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            if (c != null) {
268134c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik                c.close();
268234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik            }
268334c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        }
268434c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        return originalSyncId;
268534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik    }
268634c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik
26874755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private Cursor getColorByTypeIndex(String accountName, String accountType, long colorType,
26884755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            String colorIndex) {
26894755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        return mDb.query(Tables.COLORS, COLORS_PROJECTION, COLOR_FULL_SELECTION, new String[] {
26904755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                accountName, accountType, Long.toString(colorType), colorIndex
26914755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan        }, null, null, null);
26922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
26932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
26949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
2695f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * Gets a calendar's "owner account", i.e. the e-mail address of the owner of the calendar.
2696f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     *
2697f029d7c00095e8fff6963f301ca85196b61525e3Andy McFadden     * @param calId The calendar ID.
26989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @return email of owner or null
26999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
27009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private String getOwner(long calId) {
2701f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        if (calId < 0) {
2702f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.ERROR)) {
2703f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.e(TAG, "Calendar Id is not valid: " + calId);
2704f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
2705f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio            return null;
2706f09652d7327e45711f0e5b210e4df9c4c4c78ac4Fabrice Di Meglio        }
27079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the email address of this user from this Calendar
27089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String emailAddress = null;
27099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = null;
27109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
27119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
27129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    new String[] { Calendars.OWNER_ACCOUNT },
27139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selection */,
27149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* selectionArgs */,
27159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    null /* sort */);
27169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor == null || !cursor.moveToFirst()) {
2717f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
2718f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2719f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
27209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return null;
27219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            emailAddress = cursor.getString(0);
27239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
27249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (cursor != null) {
27259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor.close();
27269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
27279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
27289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return emailAddress;
27299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private Account getAccount(long calId) {
27322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Account account = null;
27332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor cursor = null;
27342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
27352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
27362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ACCOUNT_PROJECTION, null /* selection */, null /* selectionArgs */,
27372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    null /* sort */);
27382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor == null || !cursor.moveToFirst()) {
27392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (Log.isLoggable(TAG, Log.DEBUG)) {
27402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
27412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
27422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                return null;
27432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
27442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            account = new Account(cursor.getString(ACCOUNT_NAME_INDEX),
27452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.getString(ACCOUNT_TYPE_INDEX));
27462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
27472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (cursor != null) {
27482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                cursor.close();
27492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
27502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
27512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return account;
27522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
27532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
27549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
27559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Creates an entry in the Attendees table that refers to the given event
27569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * and that has the given response status.
27579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
27589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param eventId the event id that the new entry in the Attendees table
27599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * should refer to
27609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param status the response status
27619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param emailAddress the email of the attendee
27629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
27639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void createAttendeeEntry(long eventId, int status, String emailAddress) {
27649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
27659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.EVENT_ID, eventId);
27669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_STATUS, status);
27679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
27689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // TODO: The relationship could actually be ORGANIZER, but it will get straightened out
27699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // on sync.
27709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_RELATIONSHIP,
27719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Attendees.RELATIONSHIP_ATTENDEE);
27729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Attendees.ATTENDEE_EMAIL, emailAddress);
27739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // We don't know the ATTENDEE_NAME but that will be filled in by the
27759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // server and sent back to us.
27769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.attendeesInsert(values);
27779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
27789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
27809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Updates the attendee status in the Events table to be consistent with
27819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * the value in the Attendees table.
27829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     *
27839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * @param db the database
278424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param attendeeValues the column values for one row in the Attendees table.
27859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
27869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventAttendeeStatus(SQLiteDatabase db, ContentValues attendeeValues) {
27879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Get the event id for this attendee
278824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Long eventIdObj = attendeeValues.getAsLong(Attendees.EVENT_ID);
278924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (eventIdObj == null) {
279024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            Log.w(TAG, "Attendee update values don't include an event_id");
279124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            return;
279224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
279324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        long eventId = eventIdObj;
27949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
27959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (MULTIPLE_ATTENDEES_PER_EVENT) {
27969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the calendar id for this event
27979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Cursor cursor = null;
27989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long calId;
27999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
28009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
28019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Events.CALENDAR_ID },
28029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
28039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
28049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
28059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2806f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2807f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + eventId + " in Events table");
2808f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
28099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
28109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calId = cursor.getLong(0);
28129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
28139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
28149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
28159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the owner email for this Calendar
28199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String calendarEmail = null;
28209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            cursor = null;
28219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
28229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, calId),
28239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        new String[] { Calendars.OWNER_ACCOUNT },
28249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selection */,
28259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* selectionArgs */,
28269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        null /* sort */);
28279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor == null || !cursor.moveToFirst()) {
2828f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    if (Log.isLoggable(TAG, Log.DEBUG)) {
2829f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        Log.d(TAG, "Couldn't find " + calId + " in Calendars table");
2830f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    }
28319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return;
28329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                calendarEmail = cursor.getString(0);
28349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
28359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (cursor != null) {
28369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
28379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
28389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (calendarEmail == null) {
28419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
28429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // Get the email address for this attendee
28459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String attendeeEmail = null;
28469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (attendeeValues.containsKey(Attendees.ATTENDEE_EMAIL)) {
28479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                attendeeEmail = attendeeValues.getAsString(Attendees.ATTENDEE_EMAIL);
28489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // If the attendee email does not match the calendar email, then this
28519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // attendee is not the owner of this calendar so we don't update the
28529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // selfAttendeeStatus in the event.
28539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (!calendarEmail.equals(attendeeEmail)) {
28549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return;
28559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
285824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // Select a default value for "status" based on the relationship.
28599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        int status = Attendees.ATTENDEE_STATUS_NONE;
286024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer relationObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
286124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (relationObj != null) {
286224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            int rel = relationObj;
28639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (rel == Attendees.RELATIONSHIP_ORGANIZER) {
28649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                status = Attendees.ATTENDEE_STATUS_ACCEPTED;
28659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
28669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
286824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        // If the status is specified, use that.
286924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Integer statusObj = attendeeValues.getAsInteger(Attendees.ATTENDEE_STATUS);
287024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (statusObj != null) {
287124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            status = statusObj;
28729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
28739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
28749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues values = new ContentValues();
28759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        values.put(Events.SELF_ATTENDEE_STATUS, status);
2876b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        db.update(Tables.EVENTS, values, SQL_WHERE_ID,
2877b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                new String[] {String.valueOf(eventId)});
28789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
28799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2880d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
28811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Set the "hasAlarm" column in the database.
28821c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
28831c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param eventId The _id of the Event to update.
28841c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @param val The value to set it to (0 or 1).
28851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
28861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private void setHasAlarm(long eventId, int val) {
28871c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues values = new ContentValues();
28881c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        values.put(Events.HAS_ALARM, val);
28891c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int count = mDb.update(Tables.EVENTS, values, SQL_WHERE_ID,
28901c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                new String[] { String.valueOf(eventId) });
28911c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (count != 1) {
28921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Log.w(TAG, "setHasAlarm on event " + eventId + " updated " + count +
28931c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    " rows (expected 1)");
28941c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
28951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
28961c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
28971c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
2898d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Calculates the "last date" of the event.  For a regular event this is the start time
2899d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * plus the duration.  For a recurring event this is the start date of the last event in
2900d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence, plus the duration.  The event recurs forever, this returns -1.  If
2901d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * the recurrence rule can't be parsed, this returns -1.
2902d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
2903d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values
2904d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the date, in milliseconds, since the start of the epoch (UTC), or -1 if an
2905d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   exceptional condition exists.
2906d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @throws DateException
2907d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
29089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    long calculateLastDate(ContentValues values)
29099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            throws DateException {
29109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Allow updates to some event fields like the title or hasAlarm
29119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // without requiring DTSTART.
29129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (!values.containsKey(Events.DTSTART)) {
29139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (values.containsKey(Events.DTEND) || values.containsKey(Events.RRULE)
29149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.DURATION)
29159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EVENT_TIMEZONE)
29169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.RDATE)
29179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXRULE)
29189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    || values.containsKey(Events.EXDATE)) {
29199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new RuntimeException("DTSTART field missing from event");
29209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return -1;
29229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long dtstartMillis = values.getAsLong(Events.DTSTART);
29249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        long lastMillis = -1;
29259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Can we use dtend with a repeating event?  What does that even
29279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // mean?
29289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // NOTE: if the repeating event has a dtend, we convert it to a
29299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // duration during event processing, so this situation should not
29309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // occur.
29319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtEnd = values.getAsLong(Events.DTEND);
29329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtEnd != null) {
29339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = dtEnd;
29349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
29359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // find out how long it is
29369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Duration duration = new Duration();
29379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            String durationStr = values.getAsString(Events.DURATION);
29389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (durationStr != null) {
29399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                duration.parse(durationStr);
29409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2942f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            RecurrenceSet recur = null;
2943f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            try {
2944f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                recur = new RecurrenceSet(values);
2945f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            } catch (EventRecurrence.InvalidFormatException e) {
2946f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.WARN)) {
2947f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.w(TAG, "Could not parse RRULE recurrence string: " +
2948b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            values.get(CalendarContract.Events.RRULE), e);
2949f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
2950d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: this should throw an exception or return a distinct error code
2951f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio                return lastMillis; // -1
2952f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            }
29539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2954f8de1a9391de5d8b6a6a0ae7c55e1a2c318d6c05Fabrice Di Meglio            if (null != recur && recur.hasRecurrence()) {
29559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is repeating, so find the last date it
29569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // could appear on
29579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String tz = values.getAsString(Events.EVENT_TIMEZONE);
29599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (TextUtils.isEmpty(tz)) {
29619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    // floating timezone
29629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    tz = Time.TIMEZONE_UTC;
29639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Time dtstartLocal = new Time(tz);
29659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                dtstartLocal.set(dtstartMillis);
29679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                RecurrenceProcessor rp = new RecurrenceProcessor();
29699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = rp.getLastOccurence(dtstartLocal, recur);
29709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (lastMillis == -1) {
2971d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // repeats forever
29729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    return lastMillis;  // -1
29739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
29749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } else {
29759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // the event is not repeating, just use dtstartMillis
29769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                lastMillis = dtstartMillis;
29779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // that was the beginning of the event.  this is the end.
29809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            lastMillis = duration.addTo(lastMillis);
29819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
29829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        return lastMillis;
29839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
29849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
2985e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    /**
2986e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * Add LAST_DATE to values.
298782b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     * @param values the ContentValues (in/out); must include DTSTART and, if the event is
298882b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   recurring, the columns necessary to process a recurrence rule (RRULE, DURATION,
298982b6bf9d994d084fc8548279f3cf09eaae082430Andy McFadden     *   EVENT_TIMEZONE, etc).
2990e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     * @return values on success, null on failure
2991e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff     */
2992e604c19770482e181aa60a611b861ce5d8ed67d7Ken Shirriff    private ContentValues updateLastDate(ContentValues values) {
29939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
29949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            long last = calculateLastDate(values);
29959f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (last != -1) {
29969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                values.put(Events.LAST_DATE, last);
29979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
29989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
29999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return values;
30009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } catch (DateException e) {
30019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // don't add it if there was an error
3002f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3003f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Could not calculate last date.", e);
3004f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
30059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return null;
30069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3009d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
3010d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Creates or updates an entry in the EventsRawTimes table.
3011d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
3012d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param eventId The ID of the event that was just created or is being updated.
3013d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param values For a new event, the full set of event values; for an updated event,
3014d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *   the set of values that are being changed.
3015d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
30169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void updateEventRawTimesLocked(long eventId, ContentValues values) {
30179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ContentValues rawValues = new ContentValues();
30189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3019b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        rawValues.put(CalendarContract.EventsRawTimes.EVENT_ID, eventId);
30209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String timezone = values.getAsString(Events.EVENT_TIMEZONE);
30229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean allDay = false;
30249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
30259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDayInteger != null) {
30269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDay = allDayInteger != 0;
30279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (allDay || TextUtils.isEmpty(timezone)) {
30309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // floating timezone
30319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            timezone = Time.TIMEZONE_UTC;
30329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Time time = new Time(timezone);
30359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        time.allDay = allDay;
30369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtstartMillis = values.getAsLong(Events.DTSTART);
30379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtstartMillis != null) {
30389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtstartMillis);
3039b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTSTART_2445, time.format2445());
30409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long dtendMillis = values.getAsLong(Events.DTEND);
30439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (dtendMillis != null) {
30449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(dtendMillis);
3045b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.DTEND_2445, time.format2445());
30469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long originalInstanceMillis = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
30499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (originalInstanceMillis != null) {
30509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // This is a recurrence exception so we need to get the all-day
30519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // status of the original recurring event in order to format the
30529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // date correctly.
30539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            allDayInteger = values.getAsInteger(Events.ORIGINAL_ALL_DAY);
30549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            if (allDayInteger != null) {
30559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                time.allDay = allDayInteger != 0;
30569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
30579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(originalInstanceMillis);
3058b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.ORIGINAL_INSTANCE_TIME_2445,
3059b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    time.format2445());
30609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
30639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (lastDateMillis != null) {
30649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.allDay = allDay;
30659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            time.set(lastDateMillis);
3066b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik            rawValues.put(CalendarContract.EventsRawTimes.LAST_DATE_2445, time.format2445());
30679f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30699f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.eventsRawTimesReplace(rawValues);
30709f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
30719f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
3073b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio    protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
3074b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            boolean callerIsSyncAdapter) {
3075ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
30769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "deleteInTransaction: " + uri);
30779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
30788d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
30799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        final int match = sUriMatcher.match(uri);
30800739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_DELETE, uri, null, callerIsSyncAdapter, match,
30810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
30820ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
30830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
30849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
30859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
30869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selection, selectionArgs);
30879f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID:
30892ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
30909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
30919323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3092dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3093dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3094dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().delete(mDb, selectionWithId,
3095dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        selectionArgs);
30969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
30972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
30988d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                return deleteMatchingColors(appendAccountToSelection(uri, selection,
30998d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE),
31002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
31012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
31021ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS:
31039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31047e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                int result = 0;
31058d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(
31068d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        uri, selection, Events.ACCOUNT_NAME, Events.ACCOUNT_TYPE);
31077e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
31081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // Query this event to get the ids to delete.
3109ab472739446ef9e4a6fdcf9903d6260741d96acfErik Pasternak                Cursor cursor = mDb.query(Views.EVENTS, ID_ONLY_PROJECTION,
31101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        selection, selectionArgs, null /* groupBy */,
31117e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                        null /* having */, null /* sortOrder */);
31129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                try {
31131ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    while (cursor.moveToNext()) {
31141ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                        long id = cursor.getLong(0);
311510b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                        result += deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
31169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
3117ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                    mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
3118dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
31199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                } finally {
31209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor.close();
31219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    cursor = null;
31229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
31239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
31249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31251ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            case EVENTS_ID:
31261ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            {
31271ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                long id = ContentUris.parseId(uri);
312810b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio                return deleteEventInternal(id, callerIsSyncAdapter, false /* isBatch */);
31291ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
3130bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            case EXCEPTION_ID2:
3131bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            {
3132bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // This will throw NumberFormatException on missing or malformed input.
3133bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                List<String> segments = uri.getPathSegments();
3134bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long eventId = Long.parseLong(segments.get(1));
3135bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                long excepId = Long.parseLong(segments.get(2));
3136bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // TODO: verify that this is an exception instance (has an ORIGINAL_ID field
3137bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //       that matches the supplied eventId)
3138bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                return deleteEventInternal(excepId, callerIsSyncAdapter, false /* isBatch */);
3139bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden            }
31409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES:
31419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31427e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3143b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, selection, selectionArgs);
31447e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
31451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, selection,
31461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
31477e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case ATTENDEES_ID:
31509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31517e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
31527e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                    long id = ContentUris.parseId(uri);
3153b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.ATTENDEES, SQL_WHERE_ID,
3154b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            new String[] {String.valueOf(id)});
31557e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
31561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.ATTENDEES, uri, null /* selection */,
31572fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                                           null /* selectionArgs */);
31587e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS:
31619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, false, selection, selectionArgs, callerIsSyncAdapter);
31639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case REMINDERS_ID:
31659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31661c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                return deleteReminders(uri, true, null /*selection*/, null /*selectionArgs*/,
31671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        callerIsSyncAdapter);
31682fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
31692fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES:
31702fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
31712fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
3172b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, selection, selectionArgs);
31732fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
31741c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri, selection,
3175b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
31762fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
31772fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            }
31782fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case EXTENDED_PROPERTIES_ID:
31792fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            {
31802fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                if (callerIsSyncAdapter) {
31812fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                    long id = ContentUris.parseId(uri);
3182b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_ID,
3183636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                            new String[] {String.valueOf(id)});
31842fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                } else {
31851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.EXTENDED_PROPERTIES, uri,
31861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            null /* selection */, null /* selectionArgs */);
31877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31899f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS:
31909f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
31917e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                if (callerIsSyncAdapter) {
3192b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    return mDb.delete(Tables.CALENDAR_ALERTS, selection, selectionArgs);
31937e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                } else {
31941c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    return deleteFromEventRelatedTable(Tables.CALENDAR_ALERTS, uri, selection,
31951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            selectionArgs);
31967e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                }
31979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
31989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDAR_ALERTS_ID:
31999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
32002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
32012fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
32029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
3203b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_ID,
3204b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        new String[] {String.valueOf(id)});
32059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
32069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
32072ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                StringBuilder selectionSb = new StringBuilder(Calendars._ID + "=");
32089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selectionSb.append(uri.getPathSegments().get(1));
32099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (!TextUtils.isEmpty(selection)) {
32109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(" AND (");
32119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(selection);
32129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    selectionSb.append(')');
32139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
32149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                selection = selectionSb.toString();
321524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                // $FALL-THROUGH$ - fall through to CALENDARS for the actual delete
32169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS:
32178d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
32188d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
321974ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                return deleteMatchingCalendars(selection, selectionArgs);
32209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES:
32219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case INSTANCES_BY_DAY:
32226db535b458146a279bebd4a51d56c1bdfc204528Erik            case EVENT_DAYS:
3223315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES:
32249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new UnsupportedOperationException("Cannot delete that URL");
32259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
32269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
32279f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
32289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
32299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
323010b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio    private int deleteEventInternal(long id, boolean callerIsSyncAdapter, boolean isBatch) {
32311ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        int result = 0;
3232192b1807d4b6265a4f7581580bd6172dae3fc1b1Marc Blank        String selectionArgs[] = new String[] {String.valueOf(id)};
32331ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
32341ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        // Query this event to get the fields needed for deleting.
3235b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio        Cursor cursor = mDb.query(Tables.EVENTS, EVENTS_PROJECTION,
3236b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                SQL_WHERE_ID, selectionArgs,
3237636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                null /* groupBy */,
32381ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                null /* having */, null /* sortOrder */);
32391ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        try {
32401ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            if (cursor.moveToNext()) {
32411ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                result = 1;
32421ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String syncId = cursor.getString(EVENTS_SYNC_ID_INDEX);
324348f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                boolean emptySyncId = TextUtils.isEmpty(syncId);
32441ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
32451ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // If this was a recurring event or a recurrence
32461ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // exception, then force a recalculation of the
32471ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                // instances.
32481ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rrule = cursor.getString(EVENTS_RRULE_INDEX);
32491ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                String rdate = cursor.getString(EVENTS_RDATE_INDEX);
3250b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origId = cursor.getString(EVENTS_ORIGINAL_ID_INDEX);
3251b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                String origSyncId = cursor.getString(EVENTS_ORIGINAL_SYNC_ID_INDEX);
3252b09eb917f2490a1dae20709a667df845a2e67c94Andy McFadden                if (isRecurrenceEvent(rrule, rdate, origId, origSyncId)) {
32531ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    mMetaData.clearInstanceRange();
32541ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
32554d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                boolean isRecurrence = !TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate);
32561ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
325748f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // we clean the Events and Attendees table if the caller is CalendarSyncAdapter
325848f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                // or if the event is local (no syncId)
3259bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                //
3260bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // The EVENTS_CLEANUP_TRIGGER_SQL trigger will remove all associated data
3261bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                // (Attendees, Instances, Reminders, etc).
326248f38786c5eef920ff47bf08718be3ff94b68993Fabrice Di Meglio                if (callerIsSyncAdapter || emptySyncId) {
3263b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS, SQL_WHERE_ID, selectionArgs);
32644d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
32654d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // If this is a recurrence, and the event was never synced with the server,
32664d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we want to delete any exceptions as well.  (If it has been to the server,
32674d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // we'll let the sync adapter delete the events explicitly.)  We assume that,
32684d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // if the recurrence hasn't been synced, the exceptions haven't either.
32694d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    if (isRecurrence && emptySyncId) {
32704d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                        mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID, selectionArgs);
32714d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    }
32721ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                } else {
3273bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // Event is on the server, so we "soft delete", i.e. mark as deleted so that
3274bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the sync adapter has a chance to tell the server about the deletion.  After
3275bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // the server sees the change, the sync adapter will do the "hard delete"
3276bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden                    // (above).
32771ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                    ContentValues values = new ContentValues();
32781b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio                    values.put(Events.DELETED, 1);
3279c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Events.DIRTY, 1);
32807a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    addMutator(values, Events.MUTATORS);
3281b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.update(Tables.EVENTS, values, SQL_WHERE_ID, selectionArgs);
328202494e34ecc44c1557a9929cdaef24d603e63450Fabrice Di Meglio
32834d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // Exceptions that have been synced shouldn't be deleted -- the sync
32844d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // adapter will take care of that -- but we want to "soft delete" them so
32854d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // that they will be removed from the instances list.
32864d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // TODO: this seems to confuse the sync adapter, and leaves you with an
32874d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       invisible "ghost" event after the server sync.  Maybe we can fix
32884d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       this by making instance generation smarter?  Not vital, since the
32894d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //       exception instances disappear after the server sync.
32904d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //mDb.update(Tables.EVENTS, values, SQL_WHERE_ORIGINAL_ID_HAS_SYNC_ID,
32914d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    //        selectionArgs);
32924d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
32934d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // It's possible for the original event to be on the server but have
32944d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // exceptions that aren't.  We want to remove all events with a matching
32954d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    // original_id and an empty _sync_id.
32964d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                    mDb.delete(Tables.EVENTS, SQL_WHERE_ORIGINAL_ID_NO_SYNC_ID,
32974d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden                            selectionArgs);
32984d10d2da7bef342c2f5dcbfd91cc51a569a3998fAndy McFadden
329943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // Delete associated data; attendees, however, are deleted with the actual event
330043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    //  so that the sync adapter is able to notify attendees of the cancellation.
3301b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, selectionArgs);
3302b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EVENTS_RAW_TIMES, SQL_WHERE_EVENT_ID, selectionArgs);
3303b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.REMINDERS, SQL_WHERE_EVENT_ID, selectionArgs);
3304b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.CALENDAR_ALERTS, SQL_WHERE_EVENT_ID, selectionArgs);
3305b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                    mDb.delete(Tables.EXTENDED_PROPERTIES, SQL_WHERE_EVENT_ID,
3306b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                            selectionArgs);
33071ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff                }
33081ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            }
33091ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        } finally {
33101ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor.close();
33111ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff            cursor = null;
33121ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        }
33138f4ccb20cce4cd09bd9e0c777d2d5cd92a2c9b78Ken Shirriff
331410b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        if (!isBatch) {
3315ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak            mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
3316dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            sendUpdateNotification(callerIsSyncAdapter);
331710b51a19b296eac6c43608a7a57fb910b0e5e8bcFabrice Di Meglio        }
33181ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        return result;
33191ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff    }
33201ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
33217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
33221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Delete rows from an Event-related table (e.g. Attendees) and mark corresponding events
33231c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * as dirty.
33241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
33257e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param table The table to delete from
33267e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param uri The URI specifying the rows
33277e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selection for the query
33287e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * @param selectionArgs for the query
33297e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
33301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteFromEventRelatedTable(String table, Uri uri, String selection,
33311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            String[] selectionArgs) {
33321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (table.equals(Tables.EVENTS)) {
33331c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            throw new IllegalArgumentException("Don't delete Events with this method "
33341c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    + "(use deleteEventInternal)");
33351c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33361c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues dirtyValues = new ContentValues();
33381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        dirtyValues.put(Events.DIRTY, "1");
33397a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        addMutator(dirtyValues, Events.MUTATORS);
33401c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Re-issue the delete URI as a query.  Note that, if this is a by-ID request, the ID
33431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * will be in the URI, not selection/selectionArgs.
33441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
33451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Note that the query will return data according to the access restrictions,
33461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * so we don't need to worry about deleting data we don't have permission to read.
33471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, ID_PROJECTION, selection, selectionArgs, GENERIC_EVENT_ID);
33497e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
33507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
33511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long prevEventId = -1;
33521c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
33531c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long id = c.getLong(ID_INDEX);
33541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = c.getLong(EVENT_ID_INDEX);
33551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // Duplicate the event.  As a minor optimization, don't try to duplicate an
33561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                // event that we just duplicated on the previous iteration.
33571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
33581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDbHelper.duplicateEvent(eventId);
33591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
33609ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                mDb.delete(table, SQL_WHERE_ID, new String[]{String.valueOf(id)});
33611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                if (eventId != prevEventId) {
33621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
33631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                            new String[] { String.valueOf(eventId)} );
33641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                }
3365ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5Tony Mak                prevEventId = eventId;
33667e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
33677e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
33687e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
33697e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
33707e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
33717e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
33727e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
33737e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
33747e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    /**
33751c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Deletes rows from the Reminders table and marks the corresponding events as dirty.
33761c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * Ensures the hasAlarm column in the Event is updated.
33771c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     *
33781c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     * @return The number of rows deleted.
33791c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden     */
33801c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    private int deleteReminders(Uri uri, boolean byId, String selection, String[] selectionArgs,
33811c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            boolean callerIsSyncAdapter) {
33821c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33831c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this is a by-ID URI, make sure we have a good ID.  Also, confirm that the
33841c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * selection is null, since we will be ignoring it.
33851c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
33861c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        long rowId = -1;
33871c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
33881c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (!TextUtils.isEmpty(selection)) {
33891c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
33901c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33911c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            rowId = ContentUris.parseId(uri);
33921c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (rowId < 0) {
33931c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
33941c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
33951c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
33961c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
33971c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
33981c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Determine the set of events affected by this operation.  There can be multiple
33991c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders with the same event_id, so to avoid beating up the database with "how many
34001c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * reminders are left" and "duplicate this event" requests, we want to generate a list
34011c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * of affected event IDs and work off that.
34021c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         *
34031c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * TODO: use GROUP BY to reduce the number of rows returned in the cursor.  (The content
34041c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * provider query() doesn't take it as an argument.)
34051c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
34061c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        HashSet<Long> eventIdSet = new HashSet<Long>();
34071c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Cursor c = query(uri, new String[] { Attendees.EVENT_ID }, selection, selectionArgs, null);
34081c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        try {
34091c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (c.moveToNext()) {
34101c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                eventIdSet.add(c.getLong(0));
34111c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
34121c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        } finally {
34131c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            c.close();
34141c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
34151c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34161c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
34171c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * If this isn't a sync adapter, duplicate each event (along with its associated tables),
34181c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * and mark each as "dirty".  This is for the benefit of partial-update sync.
34191c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
34201c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (!callerIsSyncAdapter) {
34211c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            ContentValues dirtyValues = new ContentValues();
34221c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            dirtyValues.put(Events.DIRTY, "1");
34237a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            addMutator(dirtyValues, Events.MUTATORS);
34241c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34251c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Iterator<Long> iter = eventIdSet.iterator();
34261c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            while (iter.hasNext()) {
34271c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                long eventId = iter.next();
34281c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDbHelper.duplicateEvent(eventId);
34291c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
34301c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
34311c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
34321c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
34331c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34341c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
34351c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * Issue the original deletion request.  If we were called with a by-ID URI, generate
34361c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * a selection.
34371c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
34381c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        if (byId) {
34391c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selection = SQL_WHERE_ID;
34401c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
34411c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
34421c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        int delCount = mDb.delete(Tables.REMINDERS, selection, selectionArgs);
34431c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34441c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
34451c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * For each event, set "hasAlarm" to zero if we've deleted the last of the reminders.
34461c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * (If the event still has reminders, hasAlarm should already be 1.)  Because we're
34471c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * executing in an exclusive transaction there's no risk of racing against other
34481c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * database updates.
34491c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
34501c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        ContentValues noAlarmValues = new ContentValues();
34511c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        noAlarmValues.put(Events.HAS_ALARM, 0);
34521c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        Iterator<Long> iter = eventIdSet.iterator();
34531c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        while (iter.hasNext()) {
34541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            long eventId = iter.next();
34551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            // Count up the number of reminders still associated with this event.
34571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            Cursor reminders = mDb.query(Tables.REMINDERS, new String[] { GENERIC_ID },
34581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    SQL_WHERE_EVENT_ID, new String[] { String.valueOf(eventId) },
34591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                    null, null, null);
34601c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            int reminderCount = reminders.getCount();
34611c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            reminders.close();
34621c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34631c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            if (reminderCount == 0) {
34641c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                mDb.update(Tables.EVENTS, noAlarmValues, SQL_WHERE_ID,
34651c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden                        new String[] { String.valueOf(eventId) });
34661c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden            }
34671c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        }
34681c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34691c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        return delCount;
34701c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    }
34711c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
34721c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden    /**
347324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * Update rows in a table and, if this is a non-sync-adapter update, mark the corresponding
347424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * events as dirty.
347524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * <p>
347624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * This only works for tables that are associated with an event.  It is assumed that the
347724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * link to the Event row is a numeric identifier in a column called "event_id".
347824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     *
347924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param uri The original request URI.
348024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param byId Set to true if the URI is expected to include an ID.
348124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param updateValues The new values to apply.  Not all columns need be represented.
348224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selection For non-by-ID operations, the "where" clause to use.
348324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param selectionArgs For non-by-ID operations, arguments to apply to the "where" clause.
348424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @param callerIsSyncAdapter Set to true if the caller is a sync adapter.
348524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden     * @return The number of rows updated.
34867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     */
348724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    private int updateEventRelatedTable(Uri uri, String table, boolean byId,
348824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues updateValues, String selection, String[] selectionArgs,
348924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            boolean callerIsSyncAdapter)
349024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden    {
349124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
349224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Confirm that the request has either an ID or a selection, but not both.  It's not
349324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * actually "wrong" to have both, but it's not useful, and having neither is likely
349424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * a mistake.
349524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         *
349624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * If they provided an ID in the URI, convert it to an ID selection.
349724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
349824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        if (byId) {
349924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!TextUtils.isEmpty(selection)) {
350024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection not allowed for " + uri);
350124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
350224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            long rowId = ContentUris.parseId(uri);
350324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (rowId < 0) {
350424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new IllegalArgumentException("ID expected but not found in " + uri);
350524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
350624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selection = SQL_WHERE_ID;
350724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            selectionArgs = new String[] { String.valueOf(rowId) };
350824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        } else {
350924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (TextUtils.isEmpty(selection)) {
351024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new UnsupportedOperationException("Selection is required for " + uri);
351124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
351224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        }
351324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
351424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        /*
351524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * Query the events to update.  We want all the columns from the table, so we us a
351624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         * null projection.
351724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden         */
351824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden        Cursor c = mDb.query(table, null /*projection*/, selection, selectionArgs,
351924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                null, null, null);
35207e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        int count = 0;
35217e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        try {
352224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (c.getCount() == 0) {
352324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                Log.d(TAG, "No query results for " + uri + ", selection=" + selection +
352424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        " selectionArgs=" + Arrays.toString(selectionArgs));
352524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return 0;
352624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
352724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
352824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            ContentValues dirtyValues = null;
352924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (!callerIsSyncAdapter) {
353024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues = new ContentValues();
353124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                dirtyValues.put(Events.DIRTY, "1");
35327a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                addMutator(dirtyValues, Events.MUTATORS);
353324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
353424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
353524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int idIndex = c.getColumnIndex(GENERIC_ID);
353624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            final int eventIdIndex = c.getColumnIndex(GENERIC_EVENT_ID);
353724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            if (idIndex < 0 || eventIdIndex < 0) {
353824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                throw new RuntimeException("Lookup on _id/event_id failed for " + uri);
353924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            }
354024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
354124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            /*
354224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * For each row found:
354324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - merge original values with update values
354424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update database
354524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - if not sync adapter, set "dirty" flag in corresponding event to 1
354624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             * - update Event attendee status
354724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden             */
354824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            while (c.moveToNext()) {
354924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /* copy the original values into a ContentValues, then merge the changes in */
355024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                ContentValues values = new ContentValues();
355124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                DatabaseUtils.cursorRowToContentValues(c, values);
355224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                values.putAll(updateValues);
355324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
355424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long id = c.getLong(idIndex);
355524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                long eventId = c.getLong(eventIdIndex);
355624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
355724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    // Make a copy of the original, so partial-update code can see diff.
355824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDbHelper.duplicateEvent(eventId);
355924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
356024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                mDb.update(table, values, SQL_WHERE_ID, new String[] { String.valueOf(id) });
356124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (!callerIsSyncAdapter) {
356224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    mDb.update(Tables.EVENTS, dirtyValues, SQL_WHERE_ID,
356324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                            new String[] { String.valueOf(eventId) });
356424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
35657e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                count++;
356624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
356724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                /*
356824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * The Events table has a "selfAttendeeStatus" field that usually mirrors the
356924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * "attendeeStatus" column of one row in the Attendees table.  It's the provider's
357024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * job to keep these in sync, so we have to check for changes here.  (We have
357124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * to do it way down here because this is the only point where we have the
357224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * merged Attendees values.)
357324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
357424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * It's possible, but not expected, to have multiple Attendees entries with
357524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * matching attendeeEmail.  The behavior in this case is not defined.
357624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 *
357724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * We could do this more efficiently for "bulk" updates by caching the Calendar
357824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 * owner email and checking it here.
357924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                 */
358024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                if (table.equals(Tables.ATTENDEES)) {
358124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                    updateEventAttendeeStatus(mDb, values);
35828ca3274cd6bb8cf43992f9be6725c89d770011a7Sara Ting                    sendUpdateNotification(eventId, callerIsSyncAdapter);
358324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                }
35847e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            }
35857e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        } finally {
35867e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            c.close();
35877e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        }
35887e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff        return count;
35897e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff    }
35907e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
35912f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int deleteMatchingColors(String selection, String[] selectionArgs) {
35922f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // query to find all the colors that match, for each
35932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - verify no one references it
35942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        // - delete color
35952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs, null,
35962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                null, null);
35972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (c == null) {
35982f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            return 0;
35992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
36002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
36012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            Cursor c2 = null;
36022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            while (c.moveToNext()) {
36032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String index = c.getString(COLORS_COLOR_INDEX_INDEX);
36042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = c.getString(COLORS_ACCOUNT_NAME_INDEX);
36052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = c.getString(COLORS_ACCOUNT_TYPE_INDEX);
36062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                boolean isCalendarColor = c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
36072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
36082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (isCalendarColor) {
36092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = mDb.query(Tables.CALENDARS, ID_ONLY_PROJECTION,
36102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                SQL_WHERE_CALENDAR_COLOR, new String[] {
36112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                        accountName, accountType, index
36122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                }, null, null, null);
36132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
36142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
36152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " calendars.");
36162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
36172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
36182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
36192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2 = query(Events.CONTENT_URI, ID_ONLY_PROJECTION, SQL_WHERE_EVENT_COLOR,
36202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                new String[] {accountName, accountType, index}, null);
36212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (c2.getCount() != 0) {
36222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            throw new UnsupportedOperationException("Cannot delete color " + index
36232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                                    + ". Referenced by " + c2.getCount() + " events.");
36242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
36252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
36262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
36272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
36282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c2 != null) {
36292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c2.close();
36302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
36312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
36322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
36332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
36342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
36352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
36362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
36372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
36382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return mDb.delete(Tables.COLORS, selection, selectionArgs);
36392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
36402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
364174ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio    private int deleteMatchingCalendars(String selection, String[] selectionArgs) {
36429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // query to find all the calendars that match, for each
36439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar subscription
36449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // - delete calendar
364574ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        Cursor c = mDb.query(Tables.CALENDARS, sCalendarsIdProjection, selection,
364674ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                selectionArgs,
364774ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* groupBy */,
364874ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* having */,
364974ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio                null /* sortOrder */);
36509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (c == null) {
36519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return 0;
36529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
36539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
36549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            while (c.moveToNext()) {
36559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = c.getLong(CALENDARS_INDEX_ID);
36569f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                modifyCalendarSubscription(id, false /* not selected */);
36579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
36589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
36599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            c.close();
36609f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
366174ca9ba319a55a7dcb222344d2582e4dabe5d3bfFabrice Di Meglio        return mDb.delete(Tables.CALENDARS, selection, selectionArgs);
36629f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
36639f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
3664fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    private boolean doesEventExistForSyncId(String syncId) {
3665fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (syncId == null) {
3666fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
3667fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                Log.w(TAG, "SyncID cannot be null: " + syncId);
3668fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3669fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            return false;
3670fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3671fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        long count = DatabaseUtils.longForQuery(mDb, SQL_SELECT_COUNT_FOR_SYNC_ID,
3672fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                new String[] { syncId });
3673fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        return (count > 0);
3674fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3675fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3676fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Check if an UPDATE with STATUS_CANCEL means that we will need to do an Update (instead of
3677fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // a Deletion)
3678fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3679fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // Deletion will be done only and only if:
3680fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event status = canceled
3681fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // - event is a recurrence exception that does not have its original (parent) event anymore
3682fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    //
3683fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // This is due to the Server semantics that generate STATUS_CANCELED for both creation
3684fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // and deletion of a recurrence exception
3685fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    // See bug #3218104
3686d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    private boolean doesStatusCancelUpdateMeanUpdate(ContentValues values,
3687d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues modValues) {
3688d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        boolean isStatusCanceled = modValues.containsKey(Events.STATUS) &&
3689d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                (modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED);
3690fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        if (isStatusCanceled) {
3691d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            String originalSyncId = values.getAsString(Events.ORIGINAL_SYNC_ID);
3692d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3693d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!TextUtils.isEmpty(originalSyncId)) {
3694d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // This event is an exception.  See if the recurring event still exists.
3695d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                return doesEventExistForSyncId(originalSyncId);
3696d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3697d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3698d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        // This is the normal case, we just want an UPDATE
3699d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return true;
3700d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    }
3701d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
37022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private int handleUpdateColors(ContentValues values, String selection, String[] selectionArgs) {
37032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
37042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int result = mDb.update(Tables.COLORS, values, selection, selectionArgs);
37052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (values.containsKey(Colors.COLOR)) {
37062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            try {
37072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.query(Tables.COLORS, COLORS_PROJECTION, selection, selectionArgs,
37082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        null /* groupBy */, null /* having */, null /* orderBy */);
37092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                while (c.moveToNext()) {
37102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    boolean calendarColor =
37112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getInt(COLORS_COLOR_TYPE_INDEX) == Colors.TYPE_CALENDAR;
37122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    int color = c.getInt(COLORS_COLOR_INDEX);
37132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String[] args = {
37142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_NAME_INDEX),
37152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_ACCOUNT_TYPE_INDEX),
37162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            c.getString(COLORS_COLOR_INDEX_INDEX)
37172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    };
37182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    ContentValues colorValue = new ContentValues();
37192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (calendarColor) {
37202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Calendars.CALENDAR_COLOR, color);
3721d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.CALENDARS, colorValue, SQL_WHERE_CALENDAR_COLOR, args);
37222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    } else {
37232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        colorValue.put(Events.EVENT_COLOR, color);
3724d5af586101b6111ca188bb373098309c7c8a4abbAlon Albert                        mDb.update(Tables.EVENTS, colorValue, SQL_WHERE_EVENT_COLOR, args);
37252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
37262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
37272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            } finally {
37282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (c != null) {
37292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    c.close();
37302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
37312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
37322f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
37332f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return result;
37342f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
37352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
3736d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3737d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden    /**
3738d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Handles a request to update one or more events.
3739d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * <p>
3740d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * The original event(s) will be loaded from the database, merged with the new values,
3741d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * and the result checked for validity.  In some cases this will alter the supplied
3742d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * arguments (e.g. zeroing out the times on all-day events), change additional fields (e.g.
3743d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * update LAST_DATE when DTSTART changes), or cause modifications to other tables (e.g. reset
3744d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * Instances when a recurrence rule changes).
3745d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     *
3746d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param cursor The set of events to update.
37474b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden     * @param updateValues The changes to apply to each event.
3748d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @param callerIsSyncAdapter Indicates if the request comes from the sync adapter.
3749d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     * @return the number of rows updated
3750d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden     */
37514b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden    private int handleUpdateEvents(Cursor cursor, ContentValues updateValues,
3752d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean callerIsSyncAdapter) {
3753d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        /*
37541c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * This field is considered read-only.  It should not be modified by applications or
37551c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         * by the sync adapter.
37561c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden         */
37571c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        updateValues.remove(Events.HAS_ALARM);
37581c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden
37591c72909abbfe7559bcc880c339399f1eaa0478f3Andy McFadden        /*
3760d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For a single event, we can just load the event, merge modValues in, perform any
3761d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * fix-ups (putting changes into modValues), check validity, and then update().  We have
3762d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * to be careful that our fix-ups don't confuse the sync adapter.
3763d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         *
3764d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * For multiple events, we need to load, merge, and validate each event individually.
3765d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * If no single-event-specific changes need to be made, we could just issue the original
3766d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * bulk update, which would be more efficient than a series of individual updates.
3767d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * However, doing so would prevent us from taking advantage of the partial-update
3768d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         * mechanism.
3769d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden         */
3770d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        if (cursor.getCount() > 1) {
3771d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (Log.isLoggable(TAG, Log.DEBUG)) {
3772d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Log.d(TAG, "Performing update on " + cursor.getCount() + " events");
3773d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3774d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        }
3775d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        while (cursor.moveToNext()) {
37769f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Make a copy of updateValues so we can make some local changes.
37774b9f67cdc442ba0caa5bb007a4e0dfd3594ef945Andy McFadden            ContentValues modValues = new ContentValues(updateValues);
37789f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
37799f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Load the event into a ContentValues object.
3780d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            ContentValues values = new ContentValues();
3781d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            DatabaseUtils.cursorRowToContentValues(cursor, values);
37829f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            boolean doValidate = false;
37839f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (!callerIsSyncAdapter) {
37849f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                try {
37859f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // Check to see if the data in the database is valid.  If not, we will skip
37869f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // validation of the update, so that we don't blow up on attempts to
37879f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    // modify existing badly-formed events.
37889f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    validateEventData(values);
37899f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    doValidate = true;
37909f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                } catch (IllegalArgumentException iae) {
37919f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                    Log.d(TAG, "Event " + values.getAsString(Events._ID) +
37929f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            " malformed, not validating update (" +
37939f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                            iae.getMessage() + ")");
37949f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden                }
37959f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
37969f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden
37979f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Merge the modifications in.
3798d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            values.putAll(modValues);
3799d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
38002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // If a color_index is being set make sure it's valid
3801387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik            String color_id = modValues.getAsString(Events.EVENT_COLOR_KEY);
38022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (!TextUtils.isEmpty(color_id)) {
38032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountName = null;
38042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                String accountType = null;
38052f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                Cursor c = mDb.query(Tables.CALENDARS, ACCOUNT_PROJECTION, SQL_WHERE_ID,
38062f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        new String[] { values.getAsString(Events.CALENDAR_ID) }, null, null, null);
38072f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                try {
38082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c.moveToFirst()) {
38092f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountName = c.getString(ACCOUNT_NAME_INDEX);
38102f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        accountType = c.getString(ACCOUNT_TYPE_INDEX);
38112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
38122f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                } finally {
38132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (c != null) {
38142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        c.close();
38152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
38162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
38172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                verifyColorExists(accountName, accountType, color_id, Colors.TYPE_EVENT);
38182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
38192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
38209f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            // Scrub and/or validate the combined event.
3821be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            if (callerIsSyncAdapter) {
3822be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                scrubEventData(values, modValues);
38239f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            }
38249f97cde4a34eb814b2e14f694c349c5ad6003a6dAndy McFadden            if (doValidate) {
3825be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden                validateEventData(values);
3826be4ac5fac63f1619df46977891a6b4a3a0e02563Andy McFadden            }
3827d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3828d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Look for any updates that could affect LAST_DATE.  It's defined as the end of
3829d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // the last meeting, so we need to pay attention to DURATION.
3830d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.DTSTART) ||
3831d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DTEND) ||
3832d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.DURATION) ||
3833d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EVENT_TIMEZONE) ||
3834d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RRULE) ||
3835d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.RDATE) ||
3836d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXRULE) ||
3837d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    modValues.containsKey(Events.EXDATE)) {
3838d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long newLastDate;
3839d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
3840d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    newLastDate = calculateLastDate(values);
3841d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } catch (DateException de) {
3842d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    throw new IllegalArgumentException("Unable to compute LAST_DATE", de);
3843d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3844d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Long oldLastDateObj = values.getAsLong(Events.LAST_DATE);
3845d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                long oldLastDate = (oldLastDateObj == null) ? -1 : oldLastDateObj;
3846d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (oldLastDate != newLastDate) {
3847d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // This overwrites any caller-supplied LAST_DATE.  This is okay, because the
3848d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // caller isn't supposed to be messing with the LAST_DATE field.
3849d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (newLastDate < 0) {
3850d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.putNull(Events.LAST_DATE);
3851d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    } else {
3852d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        modValues.put(Events.LAST_DATE, newLastDate);
3853fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
3854fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3855d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3856d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3857d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (!callerIsSyncAdapter) {
3858d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                modValues.put(Events.DIRTY, 1);
38597a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                addMutator(modValues, Events.MUTATORS);
3860d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3861fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
3862d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // Disallow updating the attendee status in the Events
3863d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // table.  In the future, we could support this but we
3864d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // would have to query and update the attendees table
3865d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // to keep the values consistent.
3866d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (modValues.containsKey(Events.SELF_ATTENDEE_STATUS)) {
3867d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                throw new IllegalArgumentException("Updating "
3868d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + Events.SELF_ATTENDEE_STATUS
3869d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        + " in Events table is not allowed.");
3870d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3871d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3872d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (fixAllDayTime(values, modValues)) {
3873d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (Log.isLoggable(TAG, Log.WARN)) {
3874d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    Log.w(TAG, "handleUpdateEvents: " +
3875d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            "allDay is true but sec, min, hour were not 0.");
3876fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                }
3877d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            }
3878d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3879d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            // For taking care about recurrences exceptions cancelations, check if this needs
3880d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            //  to be an UPDATE or a DELETE
3881d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            boolean isUpdate = doesStatusCancelUpdateMeanUpdate(values, modValues);
3882d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3883d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            long id = values.getAsLong(Events._ID);
3884d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3885d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            if (isUpdate) {
3886d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // If a user made a change, possibly duplicate the event so we can do a partial
3887d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // update. If a sync adapter made a change and that change marks an event as
3888d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // un-dirty, remove any duplicates that may have been created earlier.
3889d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (!callerIsSyncAdapter) {
3890d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mDbHelper.duplicateEvent(id);
3891d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } else {
3892d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DIRTY)
3893d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            && modValues.getAsInteger(Events.DIRTY) == 0) {
38947a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                        modValues.put(Events.MUTATORS, (String) null);
3895d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        mDbHelper.removeDuplicateEvent(id);
3896d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3897d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3898d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                int result = mDb.update(Tables.EVENTS, modValues, SQL_WHERE_ID,
3899d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        new String[] { String.valueOf(id) });
3900d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                if (result > 0) {
3901d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    updateEventRawTimesLocked(id, modValues);
3902d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    mInstancesHelper.updateInstancesLocked(modValues, id,
3903d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            false /* not a new event */, mDb);
3904d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3905d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    // XXX: should we also be doing this when RRULE changes (e.g. instances
3906d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    //      are introduced or removed?)
3907d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (modValues.containsKey(Events.DTSTART) ||
3908d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            modValues.containsKey(Events.STATUS)) {
3909d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // If this is a cancellation knock it out
3910d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // of the instances table
3911d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (modValues.containsKey(Events.STATUS) &&
3912d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                modValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED) {
3913d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            String[] args = new String[] {String.valueOf(id)};
3914d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            mDb.delete(Tables.INSTANCES, SQL_WHERE_EVENT_ID, args);
3915d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3916d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3917d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // The start time or status of the event changed, so run the
3918d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // event alarm scheduler.
3919d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        if (Log.isLoggable(TAG, Log.DEBUG)) {
3920d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                            Log.d(TAG, "updateInternal() changing event");
3921d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        }
3922ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                        mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
3923d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
3924d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3925d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    sendUpdateNotification(id, callerIsSyncAdapter);
3926d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                }
3927d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden            } else {
3928d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                deleteEventInternal(id, callerIsSyncAdapter, true /* isBatch */);
3929ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
3930d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                sendUpdateNotification(callerIsSyncAdapter);
3931fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio            }
3932fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio        }
3933d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden
3934d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden        return cursor.getCount();
3935fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio    }
3936fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio
39379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    @Override
39389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    protected int updateInTransaction(Uri uri, ContentValues values, String selection,
3939b7c010fdc02695b692cd74acf432e8ccb3bda70cFabrice Di Meglio            String[] selectionArgs, boolean callerIsSyncAdapter) {
3940ae270e35e14b5c7a756050cb8dcccf5771743850Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.VERBOSE)) {
39419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            Log.v(TAG, "updateInTransaction: " + uri);
39429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
39438d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert        validateUriParameters(uri.getQueryParameterNames());
39440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final int match = sUriMatcher.match(uri);
39450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        verifyTransactionAllowed(TRANSACTION_UPDATE, uri, values, callerIsSyncAdapter, match,
39460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                selection, selectionArgs);
39470ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
39489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        switch (match) {
39509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE:
39519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return mDbHelper.getSyncState().update(mDb, values,
39528d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
39538d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                                Calendars.ACCOUNT_TYPE), selectionArgs);
39549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case SYNCSTATE_ID: {
39568d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                selection = appendAccountToSelection(uri, selection, Calendars.ACCOUNT_NAME,
39578d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_TYPE);
39582ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                String selectionWithId = (SyncState._ID + "=?")
3959dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        + (selection == null ? "" : " AND (" + selection + ")");
39609323bb1bbb247bac4871595a3de387ec7568897eKen Shirriff                // Prepend id to selectionArgs
3961dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                selectionArgs = insertSelectionArg(selectionArgs,
3962dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                        String.valueOf(ContentUris.parseId(uri)));
3963dc538177512191886cc40bc5e5125aae9bb197aaKen Shirriff                return mDbHelper.getSyncState().update(mDb, values, selectionWithId, selectionArgs);
39649f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
39659f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
39662f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            case COLORS:
396710651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                int validValues = 0;
396810651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                if (values.getAsInteger(Colors.COLOR) != null) {
396910651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                    validValues++;
397010651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                }
397110651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                if (values.getAsString(Colors.DATA) != null) {
397210651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                    validValues++;
397310651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                }
397410651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan
397510651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                if (values.size() != validValues) {
397610651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                    throw new UnsupportedOperationException("You may only change the COLOR and"
397710651e7b4f209a03649b9eac62facbc7bf864d17Michael Chan                            + " DATA columns for an existing Colors entry.");
39782f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
39798d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                return handleUpdateColors(values, appendAccountToSelection(uri, selection,
39808d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                        Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE),
39812f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        selectionArgs);
39822f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
398343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio            case CALENDARS:
39849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case CALENDARS_ID:
39859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
398643b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                long id;
398743b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (match == CALENDARS_ID) {
398843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    id = ContentUris.parseId(uri);
398943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                } else {
399043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // TODO: for supporting other sync adapters, we will need to
399143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // be able to deal with the following cases:
399243b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 1) selection to "_id=?" and pass in a selectionArgs
399343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 2) selection to "_id IN (1, 2, 3)"
399443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    // 3) selection to "delete=0 AND _id=1"
39954cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    if (selection != null && TextUtils.equals(selection,"_id=?")) {
39964cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                        id = Long.parseLong(selectionArgs[0]);
39974cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    } else if (selection != null && selection.startsWith("_id=")) {
399843b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // The ContentProviderOperation generates an _id=n string instead of
399943b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        // adding the id to the URL, so parse that out here.
400043b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                        id = Long.parseLong(selection.substring(4));
400143b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    } else {
4002b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                        return mDb.update(Tables.CALENDARS, values, selection, selectionArgs);
400343b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                    }
400443b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                }
400543b3eba05ef67bdd4b0a2b285b6ed2b377c136c5Fabrice Di Meglio                if (!callerIsSyncAdapter) {
4006c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                    values.put(Calendars.DIRTY, 1);
40077a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    addMutator(values, Calendars.MUTATORS);
40087a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                } else {
40097a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    if (values.containsKey(Calendars.DIRTY)
40107a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                            && values.getAsInteger(Calendars.DIRTY) == 0) {
40117a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                        values.put(Calendars.MUTATORS, (String) null);
40127a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert                    }
40132fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                }
40149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                Integer syncEvents = values.getAsInteger(Calendars.SYNC_EVENTS);
40159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (syncEvents != null) {
40169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    modifyCalendarSubscription(id, syncEvents == 1);
40179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4018387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik                String color_id = values.getAsString(Calendars.CALENDAR_COLOR_KEY);
40192f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (!TextUtils.isEmpty(color_id)) {
40202f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountName = values.getAsString(Calendars.ACCOUNT_NAME);
40212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    String accountType = values.getAsString(Calendars.ACCOUNT_TYPE);
40222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
40232f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        Account account = getAccount(id);
40242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        if (account != null) {
40252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountName = account.name;
40262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                            accountType = account.type;
40272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                        }
40282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    }
40292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    verifyColorExists(accountName, accountType, color_id, Colors.TYPE_CALENDAR);
40302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                }
40319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4032b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDARS, values, SQL_WHERE_ID,
4033636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
40349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40353ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                if (result > 0) {
4036d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    // if visibility was toggled, we need to update alarms
40374067700dbedcf4c8a379c9ecba9b5603972b4607Andy McFadden                    if (values.containsKey(Calendars.VISIBLE)) {
4038d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // pass false for removeAlarms since the call to
4039d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // scheduleNextAlarmLocked will remove any alarms for
4040d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // non-visible events anyways. removeScheduledAlarmsLocked
4041d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                        // does not actually have the effect we want
4042ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                        mCalendarAlarm.checkNextAlarm(false);
4043d74f8960b33b91b397c561662f69d8cd2e15ab20Mason Tang                    }
40443ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                    // update the widget
4045dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                    sendUpdateNotification(callerIsSyncAdapter);
40463ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang                }
40473ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
40489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                return result;
40499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
40507e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff            case EVENTS:
40519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            case EVENTS_ID:
40529f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            {
4053d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                Cursor events = null;
40549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4055d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // Grab the full set of columns for each selected event.
4056d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                // TODO: define a projection with just the data we need (e.g. we don't need to
4057d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                //       validate the SYNC_* columns)
40589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4059d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                try {
4060d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (match == EVENTS_ID) {
4061d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // Single event, identified by ID.
4062d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        long id = ContentUris.parseId(uri);
4063d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
4064d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                SQL_WHERE_ID, new String[] { String.valueOf(id) },
4065d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
40669ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    } else {
4067d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        // One or more events, identified by the selection / selectionArgs.
4068d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events = mDb.query(Tables.EVENTS, null /* columns */,
4069d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                selection, selectionArgs,
4070d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                null /* groupBy */, null /* having */, null /* sortOrder */);
40719ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert                    }
407206c305d35741db303bd3aacd0eab5af8de0ab34eErik
4073d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events.getCount() == 0) {
407424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        Log.i(TAG, "No events to update: uri=" + uri + " selection=" + selection +
4075d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                                " selectionArgs=" + Arrays.toString(selectionArgs));
4076d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        return 0;
4077d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    }
40783ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
4079d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    return handleUpdateEvents(events, values, callerIsSyncAdapter);
4080d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                } finally {
4081d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                    if (events != null) {
4082d5be35c42732d610040a64dd5bab782ef10014abAndy McFadden                        events.close();
4083fc30eb24b8ed12dec09957479f489f67cc43b42bFabrice Di Meglio                    }
40849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
40859f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
408624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES:
408724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, false, values, selection,
408824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
408924abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case ATTENDEES_ID:
409024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.ATTENDEES, true, values, null, null,
409124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
40929f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
40932fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS_ID: {
40942fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
40952fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
40969f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                long id = ContentUris.parseId(uri);
4097b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, SQL_WHERE_ID,
4098636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff                        new String[] {String.valueOf(id)});
40999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
41002fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case CALENDAR_ALERTS: {
41012fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // Note: dirty bit is not set for Alerts because it is not synced.
41022fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff                // It is generated from Reminders, which is synced.
4103b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                return mDb.update(Tables.CALENDAR_ALERTS, values, selection, selectionArgs);
41049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
410524abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
410624abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case REMINDERS:
410724abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.REMINDERS, false, values, selection,
410824abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        selectionArgs, callerIsSyncAdapter);
41092fb3bc8f9ded55c0e379e1eaed2e036a5670b63aKen Shirriff            case REMINDERS_ID: {
411024abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                int count = updateEventRelatedTable(uri, Tables.REMINDERS, true, values, null, null,
411124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        callerIsSyncAdapter);
41127e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff
41139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // Reschedule the event alarms because the
41149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // "minutes" field may have changed.
41159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                if (Log.isLoggable(TAG, Log.DEBUG)) {
41169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    Log.d(TAG, "updateInternal() changing reminder");
41179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
4118ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(false /* do not remove alarms */);
41197e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff                return count;
41209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
412124abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden
412224abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden            case EXTENDED_PROPERTIES_ID:
412324abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                return updateEventRelatedTable(uri, Tables.EXTENDED_PROPERTIES, true, values,
412424abf95cb69e8040af1a8dc2faace1bfb3ab505cAndy McFadden                        null, null, callerIsSyncAdapter);
412583512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            case SCHEDULE_ALARM_REMOVE: {
4126ea1b82d2ab660a15659258da19fabe19e5d4fbd5Tony Mak                mCalendarAlarm.checkNextAlarm(true);
412783512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff                return 0;
412883512315d187baad2c9dc3ed686cc23676c9f463Ken Shirriff            }
41299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4130315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            case PROVIDER_PROPERTIES: {
4131315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (!selection.equals("key=?")) {
4132315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Selection should be key=? for " + uri);
4133315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4134315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4135315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                List<String> list = Arrays.asList(selectionArgs);
4136315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4137315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS)) {
4138315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    throw new UnsupportedOperationException("Invalid selection key: " +
4139315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            CalendarCache.KEY_TIMEZONE_INSTANCES_PREVIOUS + " for " + uri);
4140315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4141315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4142315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Before it may be changed, save current Instances timezone for later use
4143315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                String timezoneInstancesBeforeUpdate = mCalendarCache.readTimezoneInstances();
4144315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4145315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // Update the database with the provided values (this call may change the value
4146315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // of timezone Instances)
4147b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                int result = mDb.update(Tables.CALENDAR_CACHE, values, selection, selectionArgs);
4148315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4149315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                // if successful, do some house cleaning:
4150f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "home", set the Instances
4151f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the previous
4152f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone type is set to "auto", set the Instances
4153f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone to the current
4154f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // device one
4155f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // if the timezone Instances is set AND if we are in "home"
4156f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // timezone type, then save the timezone Instance into
4157f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik                // "previous" too
4158315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                if (result > 0) {
4159315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone type...
4160315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    if (list.contains(CalendarCache.KEY_TIMEZONE_TYPE)) {
4161315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        String value = values.getAsString(CalendarCache.COLUMN_NAME_VALUE);
4162315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (value != null) {
4163315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "home"
4164315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (value.equals(CalendarCache.TIMEZONE_TYPE_HOME)) {
4165315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String previousTimezone =
4166315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                        mCalendarCache.readTimezoneInstancesPrevious();
4167315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (previousTimezone != null) {
4168315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    mCalendarCache.writeTimezoneInstances(previousTimezone);
4169315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4170315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                // Regenerate Instances if the "home" timezone has changed
4171d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                // and notify widgets
4172315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(previousTimezone) ) {
4173315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4174d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4175315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4176315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4177315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // if we are setting timezone type to "auto"
4178315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            else if (value.equals(CalendarCache.TIMEZONE_TYPE_AUTO)) {
4179315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                String localTimezone = TimeZone.getDefault().getID();
4180315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                mCalendarCache.writeTimezoneInstances(localTimezone);
4181315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                if (!timezoneInstancesBeforeUpdate.equals(localTimezone)) {
4182315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    regenerateInstancesTable();
4183d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                    sendUpdateNotification(callerIsSyncAdapter);
4184315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                }
4185315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4186315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4187315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4188315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    // If we are changing timezone Instances...
4189315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    else if (list.contains(CalendarCache.KEY_TIMEZONE_INSTANCES)) {
4190315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        // if we are in "home" timezone type...
4191315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        if (isHomeTimezone()) {
4192315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            String timezoneInstances = mCalendarCache.readTimezoneInstances();
4193315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Update the previous value
4194315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            mCalendarCache.writeTimezoneInstancesPrevious(timezoneInstances);
4195315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            // Recompute Instances if the "home" timezone has changed
4196d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                            // and send notifications to any widgets
4197315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            if (timezoneInstancesBeforeUpdate != null &&
4198315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                    !timezoneInstancesBeforeUpdate.equals(timezoneInstances)) {
4199315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                                regenerateInstancesTable();
4200d8223536b8f050ff81dfb19a6ad6b186b3941211Erik                                sendUpdateNotification(callerIsSyncAdapter);
4201315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                            }
4202315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                        }
4203315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                    }
4204315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                }
4205315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio                return result;
4206315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio            }
4207315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
42089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            default:
42099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                throw new IllegalArgumentException("Unknown URL " + uri);
42109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
42119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
42129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
42132f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    /**
42142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * Verifies that a color with the given index exists for the given Calendar
42152f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * entry.
42162f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     *
42172f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountName The email of the account the color is for
42182f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @param accountType The type of account the color is for
42194755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorIndex The color_index being set for the calendar
42204755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan     * @param colorType The type of color expected (Calendar/Event)
42212f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     * @return The color specified by the index
42222f251c778c06d21ed7693a70f4a1268ff929242eRoboErik     */
42234755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan    private int verifyColorExists(String accountName, String accountType, String colorIndex,
42244755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            int colorType) {
42252f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
42262f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            throw new IllegalArgumentException("Cannot set color. A valid account does"
42272f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    + " not exist for this calendar.");
42282f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
42292f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        int color;
42302f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
42312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        try {
42324755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            c = getColorByTypeIndex(accountName, accountType, colorType, colorIndex);
42334755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan            if (!c.moveToFirst()) {
42344755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                throw new IllegalArgumentException("Color type: " + colorType + " and index "
42354755452ab84f704f8ce4d7e0bf61a9faeeee2b99Michael Chan                        + colorIndex + " does not exist for account.");
42362f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
42372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            color = c.getInt(COLORS_COLOR_INDEX);
42382f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        } finally {
42392f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
42402f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
42412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
42422f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        }
42432f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        return color;
42442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    }
42452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
42469ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendLastSyncedColumnToSelection(String selection, Uri uri) {
42479ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (getIsCallerSyncAdapter(uri)) {
42489ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            return selection;
4249595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff        }
42509ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        final StringBuilder sb = new StringBuilder();
4251b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sb.append(CalendarContract.Events.LAST_SYNCED).append(" = 0");
42529ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return appendSelection(sb, selection);
4253595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff    }
4254595242cb01dc0d2d90b01613ff195b2be7b2559eKen Shirriff
42558d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert    private String appendAccountToSelection(
42568d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            Uri uri,
42578d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String selection,
42588d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String accountNameColumn,
42598d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            String accountTypeColumn) {
42600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountName = QueryParameterUtils.getQueryParameter(uri,
4261b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_NAME);
42620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        final String accountType = QueryParameterUtils.getQueryParameter(uri,
4263b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.EventsEntity.ACCOUNT_TYPE);
42640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (!TextUtils.isEmpty(accountName)) {
42658d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            final StringBuilder sb = new StringBuilder()
42668d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(accountNameColumn)
42678d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append("=")
42688d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(DatabaseUtils.sqlEscapeString(accountName))
42698d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(" AND ")
42708d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(accountTypeColumn)
42718d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append("=")
42728d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert                    .append(DatabaseUtils.sqlEscapeString(accountType));
42738d2ed3bf1ef3525c3a6eb17b57f07b0af35ef4d0Alon Albert            return appendSelection(sb, selection);
42749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } else {
42759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return selection;
42769f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
42779f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
42789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
42799ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    private String appendSelection(StringBuilder sb, String selection) {
42809ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        if (!TextUtils.isEmpty(selection)) {
42819ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(" AND (");
42829ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(selection);
42839ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert            sb.append(')');
42849ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        }
42859ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        return sb.toString();
42869ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert    }
42879ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert
42880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    /**
42890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * Verifies that the operation is allowed and throws an exception if it
42900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * isn't. This defines the limits of a sync adapter call vs an app call.
4291683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * <p>
4292683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * Also rejects calls that have a selection but shouldn't, or that don't have a selection
4293683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden     * but should.
4294c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik     *
42950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param type The type of call, {@link #TRANSACTION_QUERY},
42960739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_INSERT}, {@link #TRANSACTION_UPDATE}, or
42970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     *            {@link #TRANSACTION_DELETE}
42980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param uri
42990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param values
43000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     * @param isSyncAdapter
43010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik     */
43020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyTransactionAllowed(int type, Uri uri, ContentValues values,
43030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            boolean isSyncAdapter, int uriMatch, String selection, String[] selectionArgs) {
4304f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Queries are never restricted to app- or sync-adapter-only, and we don't
4305f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // restrict the set of columns that may be accessed.
4306f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        if (type == TRANSACTION_QUERY) {
4307f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            return;
4308f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4309f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
4310683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        if (type == TRANSACTION_UPDATE || type == TRANSACTION_DELETE) {
43112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            // TODO review this list, document in contract.
4312683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            if (!TextUtils.isEmpty(selection)) {
4313683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Only allow selections for the URIs that can reasonably use them.
43142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Whitelist of URIs allowed selections
4315683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4316683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case SYNCSTATE:
4317683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDARS:
4318683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4319683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4320683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case CALENDAR_ALERTS:
4321683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4322683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EXTENDED_PROPERTIES:
4323683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
43242f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    case COLORS:
4325683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4326683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4327683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection not permitted for " + uri);
4328683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4329683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            } else {
4330683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                // Disallow empty selections for some URIs.
43312f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // Blacklist of URIs _not_ allowed empty selections
4332683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                switch (uriMatch) {
4333683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case EVENTS:
4334683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case ATTENDEES:
4335683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case REMINDERS:
4336683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    case PROVIDER_PROPERTIES:
4337683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        throw new IllegalArgumentException("Selection must be specified for "
4338683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                                + uri);
4339683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                    default:
4340683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                        break;
4341683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden                }
4342683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden            }
4343683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden        }
4344683c9db3f0a244969037a7f20767a35b3187ca4bAndy McFadden
4345f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        // Only the sync adapter can use these to make changes.
43462f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        if (!isSyncAdapter) {
43472f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            switch (uriMatch) {
43482f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE:
43492f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case SYNCSTATE_ID:
43502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES:
43512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case EXTENDED_PROPERTIES_ID:
43522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                case COLORS:
43532f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    throw new IllegalArgumentException("Only sync adapters may write using " + uri);
43542f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                default:
43552f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    break;
4356f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden            }
4357f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden        }
4358f07b66b00b0ee35bddc64a6f7ac4039627fbcf89Andy McFadden
43590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (type) {
43600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_INSERT:
43610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
43620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException(
43630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                            "Inserting into instances not supported");
43640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4365c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4366c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
43670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
43680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
43690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
43700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
43710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
43720739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
43730739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
43740739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
43750739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_UPDATE:
43760739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
43770739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Updating instances not supported");
43780739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
4379c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                // Check there are no columns restricted to the provider
4380c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                verifyColumns(values, uriMatch);
43810739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
43820739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
43830739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
43840739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                } else {
43850739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that sync only columns aren't included
43860739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyNoSyncColumns(values, uriMatch);
43870739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
43880739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
43890739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case TRANSACTION_DELETE:
43900739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (uriMatch == INSTANCES) {
43910739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    throw new UnsupportedOperationException("Deleting instances not supported");
43920739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
43930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                if (isSyncAdapter) {
43940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    // check that account and account type are specified
43950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    verifyHasAccount(uri, selection, selectionArgs);
43960739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                }
43970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                return;
43980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
43990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
44000739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
44010739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyHasAccount(Uri uri, String selection, String[] selectionArgs) {
4402c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String accountName = QueryParameterUtils.getQueryParameter(uri, Calendars.ACCOUNT_NAME);
44030739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String accountType = QueryParameterUtils.getQueryParameter(uri,
4404c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Calendars.ACCOUNT_TYPE);
44050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
44060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (selection != null && selection.startsWith(ACCOUNT_SELECTION_PREFIX)) {
44070739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountName = selectionArgs[0];
44080739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                accountType = selectionArgs[1];
44090739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
44100739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
44110739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
44120739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            throw new IllegalArgumentException(
44130739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                    "Sync adapters must specify an account and account type: " + uri);
44140739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
44150739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
44160739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
4417c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private void verifyColumns(ContentValues values, int uriMatch) {
4418c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
4419c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            return;
4420c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4421c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        String[] columns;
4422c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        switch (uriMatch) {
4423c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4424c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4425c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4426c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4427c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = Events.PROVIDER_WRITABLE_COLUMNS;
4428c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4429c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            default:
4430c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                columns = PROVIDER_WRITABLE_DEFAULT_COLUMNS;
4431c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4432c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4433c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
4434c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        for (int i = 0; i < columns.length; i++) {
4435c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            if (values.containsKey(columns[i])) {
4436c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                throw new IllegalArgumentException("Only the provider may write to " + columns[i]);
4437c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            }
4438c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        }
4439c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    }
4440c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik
44410739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private void verifyNoSyncColumns(ContentValues values, int uriMatch) {
4442c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        if (values == null || values.size() == 0) {
44430739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            return;
44440739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
44450739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        String[] syncColumns;
44460739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        switch (uriMatch) {
44470739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS:
44480739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDARS_ID:
44490739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES:
44500739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            case CALENDAR_ENTITIES_ID:
4451c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Calendars.SYNC_WRITABLE_COLUMNS;
4452c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                break;
4453c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS:
4454c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENTS_ID:
4455c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES:
4456c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik            case EVENT_ENTITIES_ID:
4457c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                syncColumns = Events.SYNC_WRITABLE_COLUMNS;
44580739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
44590739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            default:
44600739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                syncColumns = SYNC_WRITABLE_DEFAULT_COLUMNS;
44610739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                break;
44620739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
44630739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
44640739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        for (int i = 0; i < syncColumns.length; i++) {
44650739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            if (values.containsKey(syncColumns[i])) {
44660739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                throw new IllegalArgumentException("Only sync adapters may write to "
44670739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik                        + syncColumns[i]);
44680739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik            }
44690739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik        }
44700739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    }
44710739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
44729f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private void modifyCalendarSubscription(long id, boolean syncEvents) {
44739f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // get the account, url, and current selected state
44749f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // for this calendar.
44759f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Cursor cursor = query(ContentUris.withAppendedId(Calendars.CONTENT_URI, id),
4476c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                new String[] {Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
4477fa332ecedc0c340109811552407142f6e4f600b2RoboErik                        Calendars.CAL_SYNC1, Calendars.SYNC_EVENTS},
44789f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selection */,
44799f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* selectionArgs */,
44809f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                null /* sort */);
44819f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
44829f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        Account account = null;
44839f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        String calendarUrl = null;
44849f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        boolean oldSyncEvents = false;
4485ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff        if (cursor != null) {
44869f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            try {
4487ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                if (cursor.moveToFirst()) {
4488ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountName = cursor.getString(0);
4489ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    final String accountType = cursor.getString(1);
4490ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    account = new Account(accountName, accountType);
4491ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    calendarUrl = cursor.getString(2);
4492ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                    oldSyncEvents = (cursor.getInt(3) != 0);
4493ae2599e63fe5e153ba735564ef3c0898d4f3c833Ken Shirriff                }
44949f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            } finally {
44952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                if (cursor != null)
44962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                    cursor.close();
44979f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
44989f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
44999f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45009535627bf6295cd94447beb83e1aac41f50c3600Erik        if (account == null) {
45019f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // should not happen?
4502f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.WARN)) {
4503f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                Log.w(TAG, "Cannot update subscription because account "
4504f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                        + "is empty -- should not happen.");
4505f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            }
45069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
45079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
45089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45099535627bf6295cd94447beb83e1aac41f50c3600Erik        if (TextUtils.isEmpty(calendarUrl)) {
45109535627bf6295cd94447beb83e1aac41f50c3600Erik            // Passing in a null Url will cause it to not add any extras
45119535627bf6295cd94447beb83e1aac41f50c3600Erik            // Should only happen for non-google calendars.
45129535627bf6295cd94447beb83e1aac41f50c3600Erik            calendarUrl = null;
45139535627bf6295cd94447beb83e1aac41f50c3600Erik        }
45149535627bf6295cd94447beb83e1aac41f50c3600Erik
45159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        if (oldSyncEvents == syncEvents) {
45169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            // nothing to do
45179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            return;
45189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
45199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
45209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // If the calendar is not selected for syncing, then don't download
45219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // events.
45229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDbHelper.scheduleSync(account, !syncEvents, calendarUrl);
45239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
45249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4525a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4526a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4527a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4528a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.
4529dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang     *
45309ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4531a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4532dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(boolean callerIsSyncAdapter) {
4533dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use -1 to represent an update to all events
4534dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(-1, callerIsSyncAdapter);
4535a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4536a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4537a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4538a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * Call this to trigger a broadcast of the ACTION_PROVIDER_CHANGED intent.
4539a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This also provides a timeout, so any calls to this method will be batched
4540a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * over a period of BROADCAST_TIMEOUT_MILLIS defined in this class.  The
4541a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * actual sending of the intent is done in
4542a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * {@link #doSendUpdateNotification()}.
4543a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
4544a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * TODO add support for eventId
4545a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
45469ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param eventId the ID of the event that changed, or -1 for no specific event
45479ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @param callerIsSyncAdapter whether or not the update is being triggered by a sync
4548a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4549dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang    private void sendUpdateNotification(long eventId,
4550dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            boolean callerIsSyncAdapter) {
4551a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        // Are there any pending broadcast requests?
4552a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        if (mBroadcastHandler.hasMessages(UPDATE_BROADCAST_MSG)) {
4553a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            // Delete any pending requests, before requeuing a fresh one
4554a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang            mBroadcastHandler.removeMessages(UPDATE_BROADCAST_MSG);
4555a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        } else {
4556dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // Because the handler does not guarantee message delivery in
4557dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // the case that the provider is killed, we need to make sure
4558dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // that the provider stays alive long enough to deliver the
4559dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // notification. This empty service is sufficient to "wedge" the
4560dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            // process until we stop it here.
4561dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang            mContext.startService(new Intent(mContext, EmptyService.class));
4562dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        }
4563dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // We use a much longer delay for sync-related updates, to prevent any
4564dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // receivers from slowing down the sync
4565dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        long delay = callerIsSyncAdapter ?
4566dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS :
4567dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang                UPDATE_BROADCAST_TIMEOUT_MILLIS;
4568dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // Despite the fact that we actually only ever use one message at a time
4569dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // for now, it is really important to call obtainMessage() to get a
4570dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // clean instance.  This avoids potentially infinite loops resulting
4571dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // adding the same instance to the message queue twice, since the
4572dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        // message queue implements its linked list using a field from Message.
4573a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG);
4574dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        mBroadcastHandler.sendMessageDelayed(msg, delay);
4575a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4576a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
4577a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    /**
4578a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * This method should not ever be called directly, to prevent sending too
4579a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     * many potentially expensive broadcasts.  Instead, call
45809ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * {@link #sendUpdateNotification(boolean)} instead.
4581a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     *
45829ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert     * @see #sendUpdateNotification(boolean)
4583a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang     */
4584a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    private void doSendUpdateNotification() {
4585a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang        Intent intent = new Intent(Intent.ACTION_PROVIDER_CHANGED,
4586b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarContract.CONTENT_URI);
4587f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        if (Log.isLoggable(TAG, Log.INFO)) {
4588f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio            Log.i(TAG, "Sending notification intent: " + intent);
4589f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio        }
4590e7a04f1fe637bc1322a6b4942e0251e3831cd544Fabrice Di Meglio        mContext.sendBroadcast(intent, null);
4591a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang    }
4592a84cc39ca05e0e799f03e04a1d3e30b5ff733cd7Mason Tang
45930739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_QUERY = 0;
45940739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_INSERT = 1;
45950739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_UPDATE = 2;
45960739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final int TRANSACTION_DELETE = 3;
45970739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
45980739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:off
45990739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    private static final String[] SYNC_WRITABLE_DEFAULT_COLUMNS = new String[] {
4600b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars.DIRTY,
4601b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        CalendarContract.Calendars._SYNC_ID
46020739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    };
4603c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    private static final String[] PROVIDER_WRITABLE_DEFAULT_COLUMNS = new String[] {
4604c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik    };
46050739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik    // @formatter:on
46060739be04415dfd61619b5611e82b7c9a6c83eae3RoboErik
46079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS = 1;
46089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int EVENTS_ID = 2;
46099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final int INSTANCES = 3;
46102ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS = 4;
46112ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDARS_ID = 5;
46122ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES = 6;
46132ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int ATTENDEES_ID = 7;
46142ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS = 8;
46152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int REMINDERS_ID = 9;
46162ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES = 10;
46172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EXTENDED_PROPERTIES_ID = 11;
46182ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS = 12;
46192ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_ID = 13;
46202ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ALERTS_BY_INSTANCE = 14;
46212ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_BY_DAY = 15;
46222ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE = 16;
46232ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SYNCSTATE_ID = 17;
46242ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES = 18;
46252ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_ENTITIES_ID = 19;
46262ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int EVENT_DAYS = 20;
46272ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int SCHEDULE_ALARM_REMOVE = 22;
46282ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int TIME = 23;
46292ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES = 24;
46302ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int CALENDAR_ENTITIES_ID = 25;
46312ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH = 26;
46322ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int INSTANCES_SEARCH_BY_DAY = 27;
46332ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik    private static final int PROVIDER_PROPERTIES = 28;
4634bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID = 29;
4635bcba82631ab0ee16efe58f0e0b0b9c18d93a6fd2Andy McFadden    private static final int EXCEPTION_ID2 = 30;
46363b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden    private static final int EMMA = 31;
46372f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final int COLORS = 32;
46389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
46399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
46409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sInstancesProjectionMap;
46412f251c778c06d21ed7693a70f4a1268ff929242eRoboErik    private static final HashMap<String, String> sColorsProjectionMap;
4642b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert    protected static final HashMap<String, String> sCalendarsProjectionMap;
4643f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    protected static final HashMap<String, String> sEventsProjectionMap;
464419fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana    private static final HashMap<String, String> sEventEntitiesProjectionMap;
46459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sAttendeesProjectionMap;
46469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sRemindersProjectionMap;
46479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    private static final HashMap<String, String> sCalendarAlertsProjectionMap;
4648315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio    private static final HashMap<String, String> sCalendarCacheProjectionMap;
464939c65e5716e21e863d8de587d139dae85f99422fFred Quintana    private static final HashMap<String, String> sCountProjectionMap;
46509f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
46519f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    static {
4652b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/when/*/*", INSTANCES);
4653b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/whenbyday/*/*", INSTANCES_BY_DAY);
4654b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/search/*/*/*", INSTANCES_SEARCH);
4655b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/searchbyday/*/*/*",
465681d904d66bd746c077cc0baa6cf1f51fe030eac4Mason Tang                INSTANCES_SEARCH_BY_DAY);
4657b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "instances/groupbyday/*/*", EVENT_DAYS);
4658b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events", EVENTS);
4659b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "events/#", EVENTS_ID);
4660b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities", EVENT_ENTITIES);
4661b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "event_entities/#", EVENT_ENTITIES_ID);
4662b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars", CALENDARS);
4663b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendars/#", CALENDARS_ID);
4664b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities", CALENDAR_ENTITIES);
4665b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_entities/#", CALENDAR_ENTITIES_ID);
4666b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees", ATTENDEES);
4667b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "attendees/#", ATTENDEES_ID);
4668b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders", REMINDERS);
4669b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "reminders/#", REMINDERS_ID);
4670b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties", EXTENDED_PROPERTIES);
4671b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "extendedproperties/#",
4672b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                EXTENDED_PROPERTIES_ID);
4673b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts", CALENDAR_ALERTS);
4674b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/#", CALENDAR_ALERTS_ID);
4675b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "calendar_alerts/by_instance",
4676b57f228c23d3672c1f08153f3fcc88ced2011714Ken Shirriff                           CALENDAR_ALERTS_BY_INSTANCE);
4677b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate", SYNCSTATE);
4678b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
4679b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY,
4680b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                CalendarAlarmManager.SCHEDULE_ALARM_REMOVE_PATH, SCHEDULE_ALARM_REMOVE);
4681b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time/#", TIME);
4682b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "time", TIME);
4683b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "properties", PROVIDER_PROPERTIES);
4684b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#", EXCEPTION_ID);
4685b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "exception/#/#", EXCEPTION_ID2);
46863b7c1cc29240628ed0f61b26375eba21665fe6d7Andy McFadden        sUriMatcher.addURI(CalendarContract.AUTHORITY, "emma", EMMA);
46872f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sUriMatcher.addURI(CalendarContract.AUTHORITY, "colors", COLORS);
46889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
468939c65e5716e21e863d8de587d139dae85f99422fFred Quintana        /** Contains just BaseColumns._COUNT */
469039c65e5716e21e863d8de587d139dae85f99422fFred Quintana        sCountProjectionMap = new HashMap<String, String>();
469108007188ce3af03ed13e9252febc9b22e1bfe953Keith Cheung        sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*) AS " + BaseColumns._COUNT);
469239c65e5716e21e863d8de587d139dae85f99422fFred Quintana
46932f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap = new HashMap<String, String>();
46942f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors._ID, Colors._ID);
46952f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.DATA, Colors.DATA);
46962f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_NAME, Colors.ACCOUNT_NAME);
46972f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.ACCOUNT_TYPE, Colors.ACCOUNT_TYPE);
4698387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sColorsProjectionMap.put(Colors.COLOR_KEY, Colors.COLOR_KEY);
46992f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR_TYPE, Colors.COLOR_TYPE);
47002f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sColorsProjectionMap.put(Colors.COLOR, Colors.COLOR);
47012f251c778c06d21ed7693a70f4a1268ff929242eRoboErik
4702b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap = new HashMap<String, String>();
4703b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars._ID, Calendars._ID);
4704b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_NAME);
4705b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.ACCOUNT_TYPE, Calendars.ACCOUNT_TYPE);
4706b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars._SYNC_ID, Calendars._SYNC_ID);
4707b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.DIRTY, Calendars.DIRTY);
47087a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        sCalendarsProjectionMap.put(Calendars.MUTATORS, Calendars.MUTATORS);
4709b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.NAME, Calendars.NAME);
4710b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(
4711b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
4712b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
4713b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR_KEY);
4714b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL,
4715b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                Calendars.CALENDAR_ACCESS_LEVEL);
4716b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
4717b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.SYNC_EVENTS, Calendars.SYNC_EVENTS);
4718b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CALENDAR_LOCATION, Calendars.CALENDAR_LOCATION);
4719b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
4720b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
4721b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.IS_PRIMARY,
4722b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                "COALESCE(" + Events.IS_PRIMARY + ", "
472308007188ce3af03ed13e9252febc9b22e1bfe953Keith Cheung                        + Calendars.OWNER_ACCOUNT + " = " + Calendars.ACCOUNT_NAME + ") AS "
472408007188ce3af03ed13e9252febc9b22e1bfe953Keith Cheung                        + Calendars.IS_PRIMARY);
4725b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND,
4726b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                Calendars.CAN_ORGANIZER_RESPOND);
4727b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
4728b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAN_PARTIALLY_UPDATE, Calendars.CAN_PARTIALLY_UPDATE);
4729b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
4730b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
4731b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.ALLOWED_AVAILABILITY, Calendars.ALLOWED_AVAILABILITY);
4732b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.ALLOWED_ATTENDEE_TYPES,
4733b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert                Calendars.ALLOWED_ATTENDEE_TYPES);
4734b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.DELETED, Calendars.DELETED);
4735b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
4736b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
4737b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
4738b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
4739b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
4740b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
4741b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
4742b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
4743b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
4744b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sCalendarsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
4745b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert
47469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sEventsProjectionMap = new HashMap<String, String>();
47479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Events columns
474802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_NAME, Events.ACCOUNT_NAME);
474902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.ACCOUNT_TYPE, Events.ACCOUNT_TYPE);
4750c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.TITLE, Events.TITLE);
4751c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4752c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4753c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.STATUS, Events.STATUS);
475402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4755387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Events.EVENT_COLOR_KEY, Events.EVENT_COLOR_KEY);
4756c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4757c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTSTART, Events.DTSTART);
4758c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DTEND, Events.DTEND);
4759c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4760c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4761c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DURATION, Events.DURATION);
4762c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4763c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4764c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4765c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4766c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES, Events.HAS_EXTENDED_PROPERTIES);
4767c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RRULE, Events.RRULE);
4768c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.RDATE, Events.RDATE);
4769c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXRULE, Events.EXRULE);
4770c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.EXDATE, Events.EXDATE);
4771c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
477234c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4773c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME, Events.ORIGINAL_INSTANCE_TIME);
4774c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4775c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4776c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4777c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4778c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS, Events.GUESTS_CAN_INVITE_OTHERS);
4779c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4780c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4781c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4782b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sEventsProjectionMap.put(Events.IS_ORGANIZER, Events.IS_ORGANIZER);
4783c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventsProjectionMap.put(Events.CUSTOM_APP_PACKAGE, Events.CUSTOM_APP_PACKAGE);
4784c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventsProjectionMap.put(Events.CUSTOM_APP_URI, Events.CUSTOM_APP_URI);
4785501e60bcb1b519d80723f7b64ba60bd079b8ec8dSara Ting        sEventsProjectionMap.put(Events.UID_2445, Events.UID_2445);
4786c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DELETED, Events.DELETED);
478702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
47889f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4789e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // Put the shared items into the Attendees, Reminders projection map
47901ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sAttendeesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
47911ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff        sRemindersProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
47921ae4c22f15c107cd9f9cd8babaa11005e45e4647Ken Shirriff
47939f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Calendar columns
4794c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR, Calendars.CALENDAR_COLOR);
4795387535fec9f646e0b7acb82d5354f2b5ebee4395RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR_KEY);
479602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CALENDAR_ACCESS_LEVEL);
4797c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.VISIBLE, Calendars.VISIBLE);
479802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_TIME_ZONE, Calendars.CALENDAR_TIME_ZONE);
4799c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Calendars.OWNER_ACCOUNT, Calendars.OWNER_ACCOUNT);
480002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CALENDAR_DISPLAY_NAME, Calendars.CALENDAR_DISPLAY_NAME);
480102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_REMINDERS, Calendars.ALLOWED_REMINDERS);
48022f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap
48032f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                .put(Calendars.ALLOWED_ATTENDEE_TYPES, Calendars.ALLOWED_ATTENDEE_TYPES);
48042f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        sEventsProjectionMap.put(Calendars.ALLOWED_AVAILABILITY, Calendars.ALLOWED_AVAILABILITY);
480502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.MAX_REMINDERS, Calendars.MAX_REMINDERS);
480602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_ORGANIZER_RESPOND, Calendars.CAN_ORGANIZER_RESPOND);
480702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAN_MODIFY_TIME_ZONE, Calendars.CAN_MODIFY_TIME_ZONE);
4808c339afc7df041ebfc5f4587f78cf38562aa23459Alon Albert        sEventsProjectionMap.put(Events.DISPLAY_COLOR, Events.DISPLAY_COLOR);
48099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
4810982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        // Put the shared items into the Instances projection map
4811e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // The Instances and CalendarAlerts are joined with Calendars, so the projections include
4812e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        // the above Calendar columns.
4813982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff        sInstancesProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4814e74157e34e174c923032a4b93ad298d0f234879cKen Shirriff        sCalendarAlertsProjectionMap = new HashMap<String, String>(sEventsProjectionMap);
4815982cbfe8b4e5af45b06fc5c18ff9e0868378ee40Ken Shirriff
4816c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events._ID, Events._ID);
481702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
481802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
481902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
482002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
482102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
482202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
48239ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
482402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
482502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
482602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
482702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
482802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
482902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
483002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
483102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
483202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
483302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
483402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
483502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
483602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventsProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
4837c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventsProjectionMap.put(Events.DIRTY, Events.DIRTY);
48387a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        sEventsProjectionMap.put(Events.MUTATORS, Events.MUTATORS);
48399ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventsProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
48409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
484146f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        sEventEntitiesProjectionMap = new HashMap<String, String>();
4842c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.TITLE, Events.TITLE);
4843c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_LOCATION, Events.EVENT_LOCATION);
4844c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DESCRIPTION, Events.DESCRIPTION);
4845c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.STATUS, Events.STATUS);
484602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR, Events.EVENT_COLOR);
4847ccfee5ca255652892b407fa046a797bf62d3b1c3Alon Albert        sEventEntitiesProjectionMap.put(Events.EVENT_COLOR_KEY, Events.EVENT_COLOR_KEY);
4848c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.SELF_ATTENDEE_STATUS, Events.SELF_ATTENDEE_STATUS);
4849c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTSTART, Events.DTSTART);
4850c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DTEND, Events.DTEND);
4851c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_TIMEZONE, Events.EVENT_TIMEZONE);
4852c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EVENT_END_TIMEZONE, Events.EVENT_END_TIMEZONE);
4853c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DURATION, Events.DURATION);
4854c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ALL_DAY, Events.ALL_DAY);
4855c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ACCESS_LEVEL, Events.ACCESS_LEVEL);
4856c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.AVAILABILITY, Events.AVAILABILITY);
4857c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ALARM, Events.HAS_ALARM);
4858c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_EXTENDED_PROPERTIES,
4859c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.HAS_EXTENDED_PROPERTIES);
4860c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RRULE, Events.RRULE);
4861c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.RDATE, Events.RDATE);
4862c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXRULE, Events.EXRULE);
4863c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.EXDATE, Events.EXDATE);
4864c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_SYNC_ID, Events.ORIGINAL_SYNC_ID);
486534c32cd924eb8ee28381106b37044b78fd8cbc30RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ID, Events.ORIGINAL_ID);
4866c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_INSTANCE_TIME,
4867c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.ORIGINAL_INSTANCE_TIME);
4868c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORIGINAL_ALL_DAY, Events.ORIGINAL_ALL_DAY);
4869c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.LAST_DATE, Events.LAST_DATE);
4870c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.HAS_ATTENDEE_DATA, Events.HAS_ATTENDEE_DATA);
4871c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.CALENDAR_ID, Events.CALENDAR_ID);
4872c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_INVITE_OTHERS,
4873c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik                Events.GUESTS_CAN_INVITE_OTHERS);
4874c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_MODIFY, Events.GUESTS_CAN_MODIFY);
4875c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.GUESTS_CAN_SEE_GUESTS, Events.GUESTS_CAN_SEE_GUESTS);
4876c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.ORGANIZER, Events.ORGANIZER);
4877b2695bf3cfb173c4c5ba7bfd3c93ba8a51d65810Alon Albert        sEventEntitiesProjectionMap.put(Events.IS_ORGANIZER, Events.IS_ORGANIZER);
4878c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventEntitiesProjectionMap.put(Events.CUSTOM_APP_PACKAGE, Events.CUSTOM_APP_PACKAGE);
4879c81732aeadada8f8bc4c216a317ba458374af2c9Michael Chan        sEventEntitiesProjectionMap.put(Events.CUSTOM_APP_URI, Events.CUSTOM_APP_URI);
4880501e60bcb1b519d80723f7b64ba60bd079b8ec8dSara Ting        sEventEntitiesProjectionMap.put(Events.UID_2445, Events.UID_2445);
4881c8383567db3ade2aea28447ad3bd09ac3033bcd7RoboErik        sEventEntitiesProjectionMap.put(Events.DELETED, Events.DELETED);
488219fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._ID, Events._ID);
488319fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana        sEventEntitiesProjectionMap.put(Events._SYNC_ID, Events._SYNC_ID);
488402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA1, Events.SYNC_DATA1);
488502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA2, Events.SYNC_DATA2);
488602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA3, Events.SYNC_DATA3);
488702f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA4, Events.SYNC_DATA4);
488802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA5, Events.SYNC_DATA5);
488902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA6, Events.SYNC_DATA6);
48909ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.SYNC_DATA7, Events.SYNC_DATA7);
489102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA8, Events.SYNC_DATA8);
489202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA9, Events.SYNC_DATA9);
489302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Events.SYNC_DATA10, Events.SYNC_DATA10);
4894470aa5bc291ca33d51dda356f38ac2954026da9aAlon Albert        sEventEntitiesProjectionMap.put(Events.DIRTY, Events.DIRTY);
48957a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        sEventEntitiesProjectionMap.put(Events.MUTATORS, Events.MUTATORS);
48969ec70fada3d8f7cf56d6b0d0947823ec5bce572cAlon Albert        sEventEntitiesProjectionMap.put(Events.LAST_SYNCED, Events.LAST_SYNCED);
4897fa332ecedc0c340109811552407142f6e4f600b2RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC1, Calendars.CAL_SYNC1);
489802f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC2, Calendars.CAL_SYNC2);
489902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC3, Calendars.CAL_SYNC3);
490002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC4, Calendars.CAL_SYNC4);
490102f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC5, Calendars.CAL_SYNC5);
490202f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC6, Calendars.CAL_SYNC6);
490302f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC7, Calendars.CAL_SYNC7);
490402f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC8, Calendars.CAL_SYNC8);
490502f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC9, Calendars.CAL_SYNC9);
490602f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sEventEntitiesProjectionMap.put(Calendars.CAL_SYNC10, Calendars.CAL_SYNC10);
490719fb3af2ec12621bca575f5518c2ba3831cb3600Fred Quintana
49089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Instances columns
49091b6beb61ef04c3da6ab0bdf8504ffecea2b9534cFabrice Di Meglio        sInstancesProjectionMap.put(Events.DELETED, "Events.deleted as deleted");
49109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.BEGIN, "begin");
49119f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END, "end");
49129f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.EVENT_ID, "Instances.event_id AS event_id");
49139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances._ID, "Instances._id AS _id");
49149f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_DAY, "startDay");
49159f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_DAY, "endDay");
49169f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.START_MINUTE, "startMinute");
49179f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sInstancesProjectionMap.put(Instances.END_MINUTE, "endMinute");
49189f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
49199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Attendees columns
49209f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.EVENT_ID, "event_id");
49219f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees._ID, "Attendees._id AS _id");
49229f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_NAME, "attendeeName");
49239f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_EMAIL, "attendeeEmail");
49249f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_STATUS, "attendeeStatus");
49259f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_RELATIONSHIP, "attendeeRelationship");
49269f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sAttendeesProjectionMap.put(Attendees.ATTENDEE_TYPE, "attendeeType");
4927bafe9de156292f65b1079dd1eb586669f573d9e6Michael Chan        sAttendeesProjectionMap.put(Attendees.ATTENDEE_IDENTITY, "attendeeIdentity");
4928bafe9de156292f65b1079dd1eb586669f573d9e6Michael Chan        sAttendeesProjectionMap.put(Attendees.ATTENDEE_ID_NAMESPACE, "attendeeIdNamespace");
492902f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
493002f97c538fc46a08d857d2c807c76fd0eec12493RoboErik        sAttendeesProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
49319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
49329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // Reminders columns
49339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.EVENT_ID, "event_id");
49349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders._ID, "Reminders._id AS _id");
49359f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.MINUTES, "minutes");
49369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sRemindersProjectionMap.put(Reminders.METHOD, "method");
4937361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events.DELETED, "Events.deleted AS deleted");
4938361695206f7a25577ddc374f20868105cae531cdAndy McFadden        sRemindersProjectionMap.put(Events._SYNC_ID, "Events._sync_id AS _sync_id");
49399f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
49409f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        // CalendarAlerts columns
49419f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.EVENT_ID, "event_id");
49429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts._ID, "CalendarAlerts._id AS _id");
49439f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.BEGIN, "begin");
49449f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.END, "end");
49459f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.ALARM_TIME, "alarmTime");
4946c3f54281b3b4a1646923a726c9a1731bd39324a0Michael Chan        sCalendarAlertsProjectionMap.put(CalendarAlerts.NOTIFY_TIME, "notifyTime");
49479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.STATE, "state");
49489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        sCalendarAlertsProjectionMap.put(CalendarAlerts.MINUTES, "minutes");
4949315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio
4950315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        // CalendarCache columns
4951315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap = new HashMap<String, String>();
4952315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_KEY, "key");
4953315d9326acd39566959f3c547225483f1fb6aefcFabrice Di Meglio        sCalendarCacheProjectionMap.put(CalendarCache.COLUMN_NAME_VALUE, "value");
49549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
49559f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
495664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
49579f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    /**
495864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * This is called by AccountManager when the set of accounts is updated.
495964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * <p>
496064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * We are overriding this since we need to delete from the
49619f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     * Calendars table, which is not syncable, which has triggers that
49627e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * will delete from the Events and  tables, which are
49637e3ec5f2025164fca508f81a5a01940bc912e064Ken Shirriff     * syncable.  TODO: update comment, make sure deletes don't get synced.
496464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     *
496564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * @param accounts The list of currently active accounts.
49669f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff     */
4967f5930ab5c4c0fe728cb8fdf923d482b4f272eb1fRoboErik    @Override
49689f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    public void onAccountsUpdated(Account[] accounts) {
496964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        Thread thread = new AccountsUpdatedThread(accounts);
497064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        thread.start();
497164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
497264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
497364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private class AccountsUpdatedThread extends Thread {
497464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        private Account[] mAccounts;
497564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
497664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        AccountsUpdatedThread(Account[] accounts) {
497764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            mAccounts = accounts;
497864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
497964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
498064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        @Override
498164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        public void run() {
498264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // The process could be killed while the thread runs.  Right now that isn't a problem,
498364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // because we'll just call removeStaleAccounts() again when the provider restarts, but
498464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // if we want to do additional actions we may need to use a service (e.g. start
498564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            // EmptyService in onAccountsUpdated() and stop it when we finish here).
498664af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
498764af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
498864af00286ccc989f390f7f43153688d4173ac62dAndy McFadden            removeStaleAccounts(mAccounts);
498964af00286ccc989f390f7f43153688d4173ac62dAndy McFadden        }
499064af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    }
499164af00286ccc989f390f7f43153688d4173ac62dAndy McFadden
499264af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    /**
499364af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     * Makes sure there are no entries for accounts that no longer exist.
499464af00286ccc989f390f7f43153688d4173ac62dAndy McFadden     */
499564af00286ccc989f390f7f43153688d4173ac62dAndy McFadden    private void removeStaleAccounts(Account[] accounts) {
49960ab307238107189b4717127b638e6c7dc9f988f1Jay Shrauner        mDb = mDbHelper.getWritableDatabase();
4997ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        if (mDb == null) {
4998ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio            return;
4999ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio        }
50009f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
500146f3f01b132f97b51ec1f4670769dda499cd9da5Ken Shirriff        HashSet<Account> validAccounts = new HashSet<Account>();
50029f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        for (Account account : accounts) {
50039f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            validAccounts.add(new Account(account.name, account.type));
50049f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
50059f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        ArrayList<Account> accountsToDelete = new ArrayList<Account>();
50069f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
50079f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        mDb.beginTransaction();
50082f251c778c06d21ed7693a70f4a1268ff929242eRoboErik        Cursor c = null;
50099f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        try {
50109f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
50112f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            for (String table : new String[]{Tables.CALENDARS, Tables.COLORS}) {
5012ae4e50b2c35fe4549d1df6568544aa72057dcbe1Fabrice Di Meglio                // Find all the accounts the calendar DB knows about, mark the ones that aren't
50139f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                // in the valid set for deletion.
50142f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = mDb.rawQuery("SELECT DISTINCT " +
50152ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_NAME +
50167cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                            "," +
50172ce7955da7dffec7819ed38be85e72df8a6f33dcRoboErik                                            Calendars.ACCOUNT_TYPE +
50187cb72fa3ea680dce378d8dac71f878e52e03f83aFabrice Di Meglio                                        " FROM " + table, null);
50199f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                while (c.moveToNext()) {
50204cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // ACCOUNT_TYPE_LOCAL is to store calendars not associated
50214cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // with a system account. Typically, a calendar must be
50224cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // associated with an account on the device or it will be
50234cd49582b08291d51cd152e1d2bff7fb547bcae2RoboErik                    // deleted.
5024b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                    if (c.getString(0) != null
5025b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && c.getString(1) != null
5026b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                            && !TextUtils.equals(c.getString(1),
5027b9644fe24edf9e25f0b21c1394e88d25070e0238RoboErik                                    CalendarContract.ACCOUNT_TYPE_LOCAL)) {
50289f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        Account currAccount = new Account(c.getString(0), c.getString(1));
50299f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        if (!validAccounts.contains(currAccount)) {
50309f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                            accountsToDelete.add(currAccount);
50319f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                        }
50329f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                    }
50339f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                }
50349f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                c.close();
50352f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c = null;
50369f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
50379f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
50389f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            for (Account account : accountsToDelete) {
5039f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                if (Log.isLoggable(TAG, Log.DEBUG)) {
5040f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                    Log.d(TAG, "removing data for removed account " + account);
5041f39a880f0554ce58ab7cf5e2e2191cb01a60fe75Fabrice Di Meglio                }
50429f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff                String[] params = new String[]{account.name, account.type};
5043b5e628f741ca3f09f31af41cc12d3c8661caf330Fabrice Di Meglio                mDb.execSQL(SQL_DELETE_FROM_CALENDARS, params);
50442f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                // This will be a no-op for accounts without a color palette.
50452f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                mDb.execSQL(SQL_DELETE_FROM_COLORS, params);
50469f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            }
50479f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDbHelper.getSyncState().onAccountsChanged(mDb, accounts);
50489f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.setTransactionSuccessful();
50499f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        } finally {
50502f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            if (c != null) {
50512f251c778c06d21ed7693a70f4a1268ff929242eRoboErik                c.close();
50522f251c778c06d21ed7693a70f4a1268ff929242eRoboErik            }
50539f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff            mDb.endTransaction();
50549f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff        }
50553ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang
50563ee5e75440f52e76bfef1aae73e2a4047ae45e7cMason Tang        // make sure the widget reflects the account changes
5057dabc4a9d60080bf97e50baad83acf2ec6c3adc07Mason Tang        sendUpdateNotification(false);
50589f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff    }
50599f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff
5060636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    /**
5061636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * Inserts an argument at the beginning of the selection arg list.
5062636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     *
5063636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
5064636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended to the user's where clause (combined with 'AND') to generate
5065636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * the final where close, so arguments associated with the QueryBuilder are
5066636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     * prepended before any user selection args to keep them in the right order.
5067636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff     */
5068636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
5069636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        if (selectionArgs == null) {
5070636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return new String[] {arg};
5071636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        } else {
5072636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            int newLength = selectionArgs.length + 1;
5073636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            String[] newSelectionArgs = new String[newLength];
5074636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            newSelectionArgs[0] = arg;
5075636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
5076636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff            return newSelectionArgs;
5077636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff        }
5078636b4fb283b9d2802afd179b20a24f2f5035ee69Ken Shirriff    }
50797a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert
50807a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    private String getCallingPackageName() {
508196d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein        if (getCachedCallingPackage() != null) {
508296d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein            // If the calling package is null, use the best available as a fallback.
508396d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein            return getCachedCallingPackage();
508496d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein        }
508596d67800c827efb5c08adaa32e221aaae53d02a4Sam Blitzstein
50867a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final PackageManager pm = getContext().getPackageManager();
50877a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final int uid = Binder.getCallingUid();
50887a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String[] packages = pm.getPackagesForUid(uid);
50897a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        if (packages != null && packages.length == 1) {
50907a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            return packages[0];
50917a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        }
50927a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String name = pm.getNameForUid(uid);
50937a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        if (name != null) {
50947a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            return name;
50957a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        }
50967a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        return String.valueOf(uid);
50977a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    }
50987a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert
50997a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    private void addMutator(ContentValues values, String columnName) {
51007a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String packageName = getCallingPackageName();
51017a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        final String mutators = values.getAsString(columnName);
51027a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        if (TextUtils.isEmpty(mutators)) {
51037a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            values.put(columnName, packageName);
51047a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        } else {
51057a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert            values.put(columnName, mutators + "," + packageName);
51067a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert        }
51077a2531a4b68e9efe14353cc7f4a64a8c5613e5d3Alon Albert    }
51089f005e4843925efe4fa8434361c4ad4ad384ed4cKen Shirriff}
5109