CalendarDatabaseHelper.java revision 1ae4c22f15c107cd9f9cd8babaa11005e45e4647
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.providers.calendar;
18
19import android.accounts.Account;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.res.Resources;
24import android.database.Cursor;
25import android.database.DatabaseUtils;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.os.Bundle;
29import android.provider.Calendar;
30import android.provider.ContactsContract;
31import android.util.Log;
32import com.android.internal.content.SyncStateContentProviderHelper;
33
34/**
35 * Database helper for calendar. Designed as a singleton to make sure that all
36 * {@link android.content.ContentProvider} users get the same reference.
37 */
38/* package */ class CalendarDatabaseHelper extends SQLiteOpenHelper {
39    private static final String TAG = "CalendarDatabaseHelper";
40
41    private static final String DATABASE_NAME = "calendar2.db";
42
43    // TODO: change the Calendar contract so these are defined there.
44    static final String ACCOUNT_NAME = "_sync_account";
45    static final String ACCOUNT_TYPE = "_sync_account_type";
46
47    // Note: if you update the version number, you must also update the code
48    // in upgradeDatabase() to modify the database (gracefully, if possible).
49    private static final int DATABASE_VERSION = 57;
50
51    private final Context mContext;
52    private final SyncStateContentProviderHelper mSyncState;
53
54    private static CalendarDatabaseHelper sSingleton = null;
55
56    private DatabaseUtils.InsertHelper mCalendarsInserter;
57    private DatabaseUtils.InsertHelper mEventsInserter;
58    private DatabaseUtils.InsertHelper mEventsRawTimesInserter;
59    private DatabaseUtils.InsertHelper mInstancesInserter;
60    private DatabaseUtils.InsertHelper mAttendeesInserter;
61    private DatabaseUtils.InsertHelper mRemindersInserter;
62    private DatabaseUtils.InsertHelper mCalendarAlertsInserter;
63    private DatabaseUtils.InsertHelper mExtendedPropertiesInserter;
64
65    public long calendarsInsert(ContentValues values) {
66        return mCalendarsInserter.insert(values);
67    }
68
69    public long eventsInsert(ContentValues values) {
70        return mEventsInserter.insert(values);
71    }
72
73    public long eventsRawTimesInsert(ContentValues values) {
74        return mEventsRawTimesInserter.insert(values);
75    }
76
77    public long eventsRawTimesReplace(ContentValues values) {
78        return mEventsRawTimesInserter.replace(values);
79    }
80
81    public long instancesInsert(ContentValues values) {
82        return mInstancesInserter.insert(values);
83    }
84
85    public long attendeesInsert(ContentValues values) {
86        return mAttendeesInserter.insert(values);
87    }
88
89    public long remindersInsert(ContentValues values) {
90        return mRemindersInserter.insert(values);
91    }
92
93    public long calendarAlertsInsert(ContentValues values) {
94        return mCalendarAlertsInserter.insert(values);
95    }
96
97    public long extendedPropertiesInsert(ContentValues values) {
98        return mExtendedPropertiesInserter.insert(values);
99    }
100
101    public static synchronized CalendarDatabaseHelper getInstance(Context context) {
102        if (sSingleton == null) {
103            sSingleton = new CalendarDatabaseHelper(context);
104        }
105        return sSingleton;
106    }
107
108    /**
109     * Private constructor, callers except unit tests should obtain an instance through
110     * {@link #getInstance(android.content.Context)} instead.
111     */
112    /* package */ CalendarDatabaseHelper(Context context) {
113        super(context, DATABASE_NAME, null, DATABASE_VERSION);
114        if (false) Log.i(TAG, "Creating OpenHelper");
115        Resources resources = context.getResources();
116
117        mContext = context;
118        mSyncState = new SyncStateContentProviderHelper();
119    }
120
121    @Override
122    public void onOpen(SQLiteDatabase db) {
123        mSyncState.onDatabaseOpened(db);
124
125        mCalendarsInserter = new DatabaseUtils.InsertHelper(db, "Calendars");
126        mEventsInserter = new DatabaseUtils.InsertHelper(db, "Events");
127        mEventsRawTimesInserter = new DatabaseUtils.InsertHelper(db, "EventsRawTimes");
128        mInstancesInserter = new DatabaseUtils.InsertHelper(db, "Instances");
129        mAttendeesInserter = new DatabaseUtils.InsertHelper(db, "Attendees");
130        mRemindersInserter = new DatabaseUtils.InsertHelper(db, "Reminders");
131        mCalendarAlertsInserter = new DatabaseUtils.InsertHelper(db, "CalendarAlerts");
132        mExtendedPropertiesInserter =
133                new DatabaseUtils.InsertHelper(db, "ExtendedProperties");
134    }
135
136    @Override
137    public void onCreate(SQLiteDatabase db) {
138        Log.i(TAG, "Bootstrapping database");
139
140        mSyncState.createDatabase(db);
141
142        db.execSQL("CREATE TABLE Calendars (" +
143                "_id INTEGER PRIMARY KEY," +
144                ACCOUNT_NAME + " TEXT," +
145                ACCOUNT_TYPE + " TEXT," +
146                "_sync_id TEXT," +
147                "_sync_version TEXT," +
148                "_sync_time TEXT," +            // UTC
149                "_sync_local_id INTEGER," +
150                "_sync_dirty INTEGER," +
151                "_sync_mark INTEGER," + // Used to filter out new rows
152                "url TEXT," +
153                "name TEXT," +
154                "displayName TEXT," +
155                "hidden INTEGER NOT NULL DEFAULT 0," +
156                "color INTEGER," +
157                "access_level INTEGER," +
158                "selected INTEGER NOT NULL DEFAULT 1," +
159                "sync_events INTEGER NOT NULL DEFAULT 0," +
160                "location TEXT," +
161                "timezone TEXT," +
162                "ownerAccount TEXT" +
163                ");");
164
165        // Trigger to remove a calendar's events when we delete the calendar
166        db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
167                "BEGIN " +
168                "DELETE FROM Events WHERE calendar_id = old._id;" +
169                "END");
170
171        // TODO: do we need both dtend and duration?
172        db.execSQL("CREATE TABLE Events (" +
173                "_id INTEGER PRIMARY KEY," +
174                ACCOUNT_NAME + " TEXT," +
175                ACCOUNT_TYPE + " TEXT," +
176                "_sync_id TEXT," +
177                "_sync_version TEXT," +
178                "_sync_time TEXT," +            // UTC
179                "_sync_local_id INTEGER," +
180                "_sync_dirty INTEGER," +
181                "_sync_mark INTEGER," + // To filter out new rows
182                "calendar_id INTEGER NOT NULL," +
183                "htmlUri TEXT," +
184                "title TEXT," +
185                "eventLocation TEXT," +
186                "description TEXT," +
187                "eventStatus INTEGER," +
188                "selfAttendeeStatus INTEGER NOT NULL DEFAULT 0," +
189                "commentsUri TEXT," +
190                "dtstart INTEGER," +               // millis since epoch
191                "dtend INTEGER," +                 // millis since epoch
192                "eventTimezone TEXT," +         // timezone for event
193                "duration TEXT," +
194                "allDay INTEGER NOT NULL DEFAULT 0," +
195                "visibility INTEGER NOT NULL DEFAULT 0," +
196                "transparency INTEGER NOT NULL DEFAULT 0," +
197                "hasAlarm INTEGER NOT NULL DEFAULT 0," +
198                "hasExtendedProperties INTEGER NOT NULL DEFAULT 0," +
199                "rrule TEXT," +
200                "rdate TEXT," +
201                "exrule TEXT," +
202                "exdate TEXT," +
203                "originalEvent TEXT," +  // _sync_id of recurring event
204                "originalInstanceTime INTEGER," +  // millis since epoch
205                "originalAllDay INTEGER," +
206                "lastDate INTEGER," +               // millis since epoch
207                "hasAttendeeData INTEGER NOT NULL DEFAULT 0," +
208                "guestsCanModify INTEGER NOT NULL DEFAULT 0," +
209                "guestsCanInviteOthers INTEGER NOT NULL DEFAULT 1," +
210                "guestsCanSeeGuests INTEGER NOT NULL DEFAULT 1," +
211                "organizer STRING," +
212                "deleted INTEGER NOT NULL DEFAULT 0" +
213                ");");
214
215        db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
216                + Calendar.Events._SYNC_ACCOUNT_TYPE + ", " + Calendar.Events._SYNC_ACCOUNT + ", "
217                + Calendar.Events._SYNC_ID + ");");
218
219        db.execSQL("CREATE INDEX eventsCalendarIdIndex ON Events (" +
220                Calendar.Events.CALENDAR_ID +
221                ");");
222
223        db.execSQL("CREATE TABLE EventsRawTimes (" +
224                "_id INTEGER PRIMARY KEY," +
225                "event_id INTEGER NOT NULL," +
226                "dtstart2445 TEXT," +
227                "dtend2445 TEXT," +
228                "originalInstanceTime2445 TEXT," +
229                "lastDate2445 TEXT," +
230                "UNIQUE (event_id)" +
231                ");");
232
233        db.execSQL("CREATE TABLE Instances (" +
234                "_id INTEGER PRIMARY KEY," +
235                "event_id INTEGER," +
236                "begin INTEGER," +         // UTC millis
237                "end INTEGER," +           // UTC millis
238                "startDay INTEGER," +      // Julian start day
239                "endDay INTEGER," +        // Julian end day
240                "startMinute INTEGER," +   // minutes from midnight
241                "endMinute INTEGER," +     // minutes from midnight
242                "UNIQUE (event_id, begin, end)" +
243                ");");
244
245        db.execSQL("CREATE INDEX instancesStartDayIndex ON Instances (" +
246                Calendar.Instances.START_DAY +
247                ");");
248
249        db.execSQL("CREATE TABLE CalendarMetaData (" +
250                "_id INTEGER PRIMARY KEY," +
251                "localTimezone TEXT," +
252                "minInstance INTEGER," +      // UTC millis
253                "maxInstance INTEGER," +      // UTC millis
254                "minBusyBits INTEGER," +      // UTC millis
255                "maxBusyBits INTEGER" +       // UTC millis
256                ");");
257
258        db.execSQL("CREATE TABLE BusyBits(" +
259                "day INTEGER PRIMARY KEY," +  // the Julian day
260                "busyBits INTEGER," +         // 24 bits for 60-minute intervals
261                "allDayCount INTEGER" +       // number of all-day events
262                ");");
263
264        db.execSQL("CREATE TABLE Attendees (" +
265                "_id INTEGER PRIMARY KEY," +
266                "event_id INTEGER," +
267                "attendeeName TEXT," +
268                "attendeeEmail TEXT," +
269                "attendeeStatus INTEGER," +
270                "attendeeRelationship INTEGER," +
271                "attendeeType INTEGER" +
272                ");");
273
274        db.execSQL("CREATE INDEX attendeesEventIdIndex ON Attendees (" +
275                Calendar.Attendees.EVENT_ID +
276                ");");
277
278        db.execSQL("CREATE TABLE Reminders (" +
279                "_id INTEGER PRIMARY KEY," +
280                "event_id INTEGER," +
281                "minutes INTEGER," +
282                "method INTEGER NOT NULL" +
283                " DEFAULT " + Calendar.Reminders.METHOD_DEFAULT +
284                ");");
285
286        db.execSQL("CREATE INDEX remindersEventIdIndex ON Reminders (" +
287                Calendar.Reminders.EVENT_ID +
288                ");");
289
290         // This table stores the Calendar notifications that have gone off.
291        db.execSQL("CREATE TABLE CalendarAlerts (" +
292                "_id INTEGER PRIMARY KEY," +
293                "event_id INTEGER," +
294                "begin INTEGER NOT NULL," +         // UTC millis
295                "end INTEGER NOT NULL," +           // UTC millis
296                "alarmTime INTEGER NOT NULL," +     // UTC millis
297                "creationTime INTEGER NOT NULL," +  // UTC millis
298                "receivedTime INTEGER NOT NULL," +  // UTC millis
299                "notifyTime INTEGER NOT NULL," +    // UTC millis
300                "state INTEGER NOT NULL," +
301                "minutes INTEGER," +
302                "UNIQUE (alarmTime, begin, event_id)" +
303                ");");
304
305        db.execSQL("CREATE INDEX calendarAlertsEventIdIndex ON CalendarAlerts (" +
306                Calendar.CalendarAlerts.EVENT_ID +
307                ");");
308
309        db.execSQL("CREATE TABLE ExtendedProperties (" +
310                "_id INTEGER PRIMARY KEY," +
311                "event_id INTEGER," +
312                "name TEXT," +
313                "value TEXT" +
314                ");");
315
316        db.execSQL("CREATE INDEX extendedPropertiesEventIdIndex ON ExtendedProperties (" +
317                Calendar.ExtendedProperties.EVENT_ID +
318                ");");
319
320        // Trigger to remove data tied to an event when we delete that event.
321        db.execSQL("CREATE TRIGGER events_cleanup_delete DELETE ON Events " +
322                "BEGIN " +
323                "DELETE FROM Instances WHERE event_id = old._id;" +
324                "DELETE FROM EventsRawTimes WHERE event_id = old._id;" +
325                "DELETE FROM Attendees WHERE event_id = old._id;" +
326                "DELETE FROM Reminders WHERE event_id = old._id;" +
327                "DELETE FROM CalendarAlerts WHERE event_id = old._id;" +
328                "DELETE FROM ExtendedProperties WHERE event_id = old._id;" +
329                "END");
330
331        createEventsView(db);
332
333        ContentResolver.requestSync(null /* all accounts */,
334                ContactsContract.AUTHORITY, new Bundle());
335    }
336
337    @Override
338    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
339        Log.i(TAG, "Upgrading DB from version " + oldVersion
340                + " to " + newVersion);
341        if (oldVersion < 46) {
342            dropTables(db);
343            mSyncState.createDatabase(db);
344            return; // this was lossy
345        }
346
347        if (oldVersion == 46) {
348            Log.w(TAG, "Upgrading CalendarAlerts table");
349            db.execSQL("UPDATE CalendarAlerts SET reminder_id=NULL;");
350            db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN minutes INTEGER DEFAULT 0;");
351            oldVersion += 1;
352        }
353
354        if (oldVersion == 47) {
355            // Changing to version 48 was intended to force a data wipe
356            dropTables(db);
357            mSyncState.createDatabase(db);
358            return; // this was lossy
359        }
360
361        if (oldVersion == 48) {
362            // Changing to version 49 was intended to force a data wipe
363            dropTables(db);
364            mSyncState.createDatabase(db);
365            return; // this was lossy
366        }
367
368        if (oldVersion == 49) {
369            Log.w(TAG, "Upgrading DeletedEvents table");
370
371            // We don't have enough information to fill in the correct
372            // value of the calendar_id for old rows in the DeletedEvents
373            // table, but rows in that table are transient so it is unlikely
374            // that there are any rows.  Plus, the calendar_id is used only
375            // when deleting a calendar, which is a rare event.  All new rows
376            // will have the correct calendar_id.
377            db.execSQL("ALTER TABLE DeletedEvents ADD COLUMN calendar_id INTEGER;");
378
379            // Trigger to remove a calendar's events when we delete the calendar
380            db.execSQL("DROP TRIGGER IF EXISTS calendar_cleanup");
381            db.execSQL("CREATE TRIGGER calendar_cleanup DELETE ON Calendars " +
382                    "BEGIN " +
383                    "DELETE FROM Events WHERE calendar_id = old._id;" +
384                    "DELETE FROM DeletedEvents WHERE calendar_id = old._id;" +
385                    "END");
386            db.execSQL("DROP TRIGGER IF EXISTS event_to_deleted");
387            oldVersion += 1;
388        }
389
390        if (oldVersion == 50) {
391            // This should have been deleted in the upgrade from version 49
392            // but we missed it.
393            db.execSQL("DROP TRIGGER IF EXISTS event_to_deleted");
394            oldVersion += 1;
395        }
396
397        if (oldVersion == 51) {
398            // We added "originalAllDay" to the Events table to keep track of
399            // the allDay status of the original recurring event for entries
400            // that are exceptions to that recurring event.  We need this so
401            // that we can format the date correctly for the "originalInstanceTime"
402            // column when we make a change to the recurrence exception and
403            // send it to the server.
404            db.execSQL("ALTER TABLE Events ADD COLUMN originalAllDay INTEGER;");
405
406            // Iterate through the Events table and for each recurrence
407            // exception, fill in the correct value for "originalAllDay",
408            // if possible.  The only times where this might not be possible
409            // are (1) the original recurring event no longer exists, or
410            // (2) the original recurring event does not yet have a _sync_id
411            // because it was created on the phone and hasn't been synced to the
412            // server yet.  In both cases the originalAllDay field will be set
413            // to null.  In the first case we don't care because the recurrence
414            // exception will not be displayed and we won't be able to make
415            // any changes to it (and even if we did, the server should ignore
416            // them, right?).  In the second case, the calendar client already
417            // disallows making changes to an instance of a recurring event
418            // until the recurring event has been synced to the server so the
419            // second case should never occur.
420
421            // "cursor" iterates over all the recurrences exceptions.
422            Cursor cursor = db.rawQuery("SELECT _id,originalEvent FROM Events"
423                    + " WHERE originalEvent IS NOT NULL", null /* selection args */);
424            if (cursor != null) {
425                try {
426                    while (cursor.moveToNext()) {
427                        long id = cursor.getLong(0);
428                        String originalEvent = cursor.getString(1);
429
430                        // Find the original recurring event (if it exists)
431                        Cursor recur = db.rawQuery("SELECT allDay FROM Events"
432                                + " WHERE _sync_id=?", new String[] {originalEvent});
433                        if (recur == null) {
434                            continue;
435                        }
436
437                        try {
438                            // Fill in the "originalAllDay" field of the
439                            // recurrence exception with the "allDay" value
440                            // from the recurring event.
441                            if (recur.moveToNext()) {
442                                int allDay = recur.getInt(0);
443                                db.execSQL("UPDATE Events SET originalAllDay=" + allDay
444                                        + " WHERE _id="+id);
445                            }
446                        } finally {
447                            recur.close();
448                        }
449                    }
450                } finally {
451                    cursor.close();
452                }
453            }
454            oldVersion += 1;
455        }
456
457        if (oldVersion == 52) {
458            Log.w(TAG, "Upgrading CalendarAlerts table");
459            db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN creationTime INTEGER DEFAULT 0;");
460            db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN receivedTime INTEGER DEFAULT 0;");
461            db.execSQL("ALTER TABLE CalendarAlerts ADD COLUMN notifyTime INTEGER DEFAULT 0;");
462            oldVersion += 1;
463        }
464
465        if (oldVersion == 53) {
466            Log.w(TAG, "adding eventSyncAccountAndIdIndex");
467            db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
468                    + Calendar.Events._SYNC_ACCOUNT + ", " + Calendar.Events._SYNC_ID + ");");
469            oldVersion += 1;
470        }
471
472        if (oldVersion == 54) {
473            db.execSQL("ALTER TABLE Calendars ADD COLUMN _sync_account_type TEXT;");
474            db.execSQL("ALTER TABLE Events ADD COLUMN _sync_account_type TEXT;");
475            db.execSQL("ALTER TABLE DeletedEvents ADD COLUMN _sync_account_type TEXT;");
476            db.execSQL("UPDATE Calendars"
477                    + " SET _sync_account_type='com.google'"
478                    + " WHERE _sync_account IS NOT NULL");
479            db.execSQL("UPDATE Events"
480                    + " SET _sync_account_type='com.google'"
481                    + " WHERE _sync_account IS NOT NULL");
482            db.execSQL("UPDATE DeletedEvents"
483                    + " SET _sync_account_type='com.google'"
484                    + " WHERE _sync_account IS NOT NULL");
485            Log.w(TAG, "re-creating eventSyncAccountAndIdIndex");
486            db.execSQL("DROP INDEX eventSyncAccountAndIdIndex");
487            db.execSQL("CREATE INDEX eventSyncAccountAndIdIndex ON Events ("
488                    + Calendar.Events._SYNC_ACCOUNT_TYPE + ", "
489                    + Calendar.Events._SYNC_ACCOUNT + ", "
490                    + Calendar.Events._SYNC_ID + ");");
491            oldVersion += 1;
492        }
493        if (oldVersion == 55 || oldVersion == 56) {  // Both require resync
494            // Delete sync state, so all records will be re-synced.
495            db.execSQL("DELETE FROM _sync_state;");
496
497            // "cursor" iterates over all the calendars
498            Cursor cursor = db.rawQuery("SELECT _sync_account,_sync_account_type,url "
499                    + "FROM Calendars",
500                    null /* selection args */);
501            if (cursor != null) {
502                try {
503                    while (cursor.moveToNext()) {
504                        String accountName = cursor.getString(0);
505                        String accountType = cursor.getString(1);
506                        final Account account = new Account(accountName, accountType);
507                        String calendarUrl = cursor.getString(2);
508                        scheduleSync(account, false /* two-way sync */, calendarUrl);
509                    }
510                } finally {
511                    cursor.close();
512                }
513            }
514        }
515        if (oldVersion == 55) {
516            db.execSQL("ALTER TABLE Calendars ADD COLUMN ownerAccount TEXT;");
517            db.execSQL("ALTER TABLE Events ADD COLUMN hasAttendeeData INTEGER;");
518            // Clear _sync_dirty to avoid a client-to-server sync that could blow away
519            // server attendees.
520            // Clear _sync_version to pull down the server's event (with attendees)
521            // Change the URLs from full-selfattendance to full
522            db.execSQL("UPDATE Events"
523                    + " SET _sync_dirty=0,"
524                    + " _sync_version=NULL,"
525                    + " _sync_id="
526                    + "REPLACE(_sync_id, '/private/full-selfattendance', '/private/full'),"
527                    + " commentsUri ="
528                    + "REPLACE(commentsUri, '/private/full-selfattendance', '/private/full');");
529            db.execSQL("UPDATE Calendars"
530                    + " SET url="
531                    + "REPLACE(url, '/private/full-selfattendance', '/private/full');");
532
533            // "cursor" iterates over all the calendars
534            Cursor cursor = db.rawQuery("SELECT _id, url FROM Calendars",
535                    null /* selection args */);
536            // Add the owner column.
537            if (cursor != null) {
538                try {
539                    while (cursor.moveToNext()) {
540                        Long id = cursor.getLong(0);
541                        String url = cursor.getString(1);
542                        String owner = CalendarSyncAdapter.calendarEmailAddressFromFeedUrl(url);
543                        db.execSQL("UPDATE Calendars SET ownerAccount=? WHERE _id=?",
544                                new Object[] {owner, id});
545                    }
546                } finally {
547                    cursor.close();
548                }
549            }
550            oldVersion += 1;
551        }
552        if (oldVersion == 56) {
553            db.execSQL("ALTER TABLE Events ADD COLUMN guestsCanModify"
554                    + " INTEGER NOT NULL DEFAULT 0;");
555            db.execSQL("ALTER TABLE Events ADD COLUMN guestsCanInviteOthers"
556                    + " INTEGER NOT NULL DEFAULT 1;");
557            db.execSQL("ALTER TABLE Events ADD COLUMN guestsCanSeeGuests"
558                    + " INTEGER NOT NULL DEFAULT 1;");
559            db.execSQL("ALTER TABLE Events ADD COLUMN organizer STRING;");
560            db.execSQL("UPDATE Events SET organizer="
561                    + "(SELECT attendeeEmail FROM Attendees WHERE "
562                    + "Attendees.event_id = Events._id"
563                    + " AND Attendees.attendeeRelationship=2);");
564            oldVersion += 1;
565        }
566    }
567
568    private void dropTables(SQLiteDatabase db) {
569        db.execSQL("DROP TABLE IF EXISTS Calendars;");
570        db.execSQL("DROP TABLE IF EXISTS Events;");
571        db.execSQL("DROP TABLE IF EXISTS EventsRawTimes;");
572        db.execSQL("DROP TABLE IF EXISTS Instances;");
573        db.execSQL("DROP TABLE IF EXISTS CalendarMetaData;");
574        db.execSQL("DROP TABLE IF EXISTS BusyBits;");
575        db.execSQL("DROP TABLE IF EXISTS Attendees;");
576        db.execSQL("DROP TABLE IF EXISTS Reminders;");
577        db.execSQL("DROP TABLE IF EXISTS CalendarAlerts;");
578        db.execSQL("DROP TABLE IF EXISTS ExtendedProperties;");
579    }
580
581    @Override
582    public synchronized SQLiteDatabase getWritableDatabase() {
583        SQLiteDatabase db = super.getWritableDatabase();
584        return db;
585    }
586
587    public SyncStateContentProviderHelper getSyncState() {
588        return mSyncState;
589    }
590
591    /**
592     * Schedule a calendar sync for the account.
593     * @param account the account for which to schedule a sync
594     * @param uploadChangesOnly if set, specify that the sync should only send
595     *   up local changes.  This is typically used for a local sync, a user override of
596     *   too many deletions, or a sync after a calendar is unselected.
597     * @param url the url feed for the calendar to sync (may be null, in which case a poll of
598     *   all feeds is done.)
599     */
600    void scheduleSync(Account account, boolean uploadChangesOnly, String url) {
601        Bundle extras = new Bundle();
602        if (uploadChangesOnly) {
603            extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly);
604        }
605        if (url != null) {
606            extras.putString("feed", url);
607            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
608        }
609        ContentResolver.requestSync(account, Calendar.Calendars.CONTENT_URI.getAuthority(), extras);
610    }
611
612    public void wipeData() {
613        SQLiteDatabase db = getWritableDatabase();
614
615        db.execSQL("DELETE FROM Calendars;");
616        db.execSQL("DELETE FROM Events;");
617        db.execSQL("DELETE FROM EventsRawTimes;");
618        db.execSQL("DELETE FROM Instances;");
619        db.execSQL("DELETE FROM CalendarMetaData;");
620        db.execSQL("DELETE FROM BusyBits;");
621        db.execSQL("DELETE FROM Attendees;");
622        db.execSQL("DELETE FROM Reminders;");
623        db.execSQL("DELETE FROM CalendarAlerts;");
624        db.execSQL("DELETE FROM ExtendedProperties;");
625    }
626
627    public interface Views {
628      public static final String EVENTS = "view_events";
629    }
630
631    public interface Tables {
632      public static final String EVENTS = "Events";
633      public static final String CALENDARS = "Calendars";
634    }
635
636    private static void createEventsView(SQLiteDatabase db) {
637        db.execSQL("DROP VIEW IF EXISTS " + Views.EVENTS + ";");
638        String eventsSelect = "SELECT "
639                + Tables.EVENTS + "." + Calendar.Events._ID + " AS " + Calendar.Events._ID + ","
640                + Calendar.Events.HTML_URI + ","
641                + Calendar.Events.TITLE + ","
642                + Calendar.Events.DESCRIPTION + ","
643                + Calendar.Events.EVENT_LOCATION + ","
644                + Calendar.Events.STATUS + ","
645                + Calendar.Events.SELF_ATTENDEE_STATUS + ","
646                + Calendar.Events.COMMENTS_URI + ","
647                + Calendar.Events.DTSTART + ","
648                + Calendar.Events.DTEND + ","
649                + Calendar.Events.DURATION + ","
650                + Calendar.Events.EVENT_TIMEZONE + ","
651                + Calendar.Events.ALL_DAY + ","
652                + Calendar.Events.VISIBILITY + ","
653                + Calendar.Events.TIMEZONE + ","
654                + Calendar.Events.SELECTED + ","
655                + Calendar.Events.ACCESS_LEVEL + ","
656                + Calendar.Events.TRANSPARENCY + ","
657                + Calendar.Events.COLOR + ","
658                + Calendar.Events.HAS_ALARM + ","
659                + Calendar.Events.HAS_EXTENDED_PROPERTIES + ","
660                + Calendar.Events.RRULE + ","
661                + Calendar.Events.RDATE + ","
662                + Calendar.Events.EXRULE + ","
663                + Calendar.Events.EXDATE + ","
664                + Calendar.Events.ORIGINAL_EVENT + ","
665                + Calendar.Events.ORIGINAL_INSTANCE_TIME + ","
666                + Calendar.Events.ORIGINAL_ALL_DAY + ","
667                + Calendar.Events.LAST_DATE + ","
668                + Calendar.Events.HAS_ATTENDEE_DATA + ","
669                + Calendar.Events.CALENDAR_ID + ","
670                + Calendar.Events.GUESTS_CAN_INVITE_OTHERS + ","
671                + Calendar.Events.GUESTS_CAN_MODIFY + ","
672                + Calendar.Events.GUESTS_CAN_SEE_GUESTS + ","
673                + Calendar.Events.ORGANIZER + ","
674                + Calendar.Events.DELETED + ","
675                + Tables.EVENTS + "." + Calendar.Events._SYNC_ID
676                + " AS " + Calendar.Events._SYNC_ID + ","
677                + Tables.EVENTS + "." + Calendar.Events._SYNC_VERSION
678                + " AS " + Calendar.Events._SYNC_VERSION + ","
679                + Tables.EVENTS + "." + Calendar.Events._SYNC_DIRTY
680                + " AS " + Calendar.Events._SYNC_DIRTY + ","
681                + Tables.EVENTS + "." + Calendar.Events._SYNC_ACCOUNT
682                + " AS " + Calendar.Events._SYNC_ACCOUNT + ","
683                + Tables.EVENTS + "." + Calendar.Events._SYNC_ACCOUNT_TYPE
684                + " AS " + Calendar.Events._SYNC_ACCOUNT_TYPE + ","
685                + Tables.EVENTS + "." + Calendar.Events._SYNC_TIME
686                + " AS " + Calendar.Events._SYNC_TIME + ","
687                + Tables.EVENTS + "." + Calendar.Events._SYNC_LOCAL_ID
688                + " AS " + Calendar.Events._SYNC_LOCAL_ID + ","
689                + Calendar.Calendars.URL + ","
690                + Calendar.Calendars.OWNER_ACCOUNT
691                + " FROM " + Tables.EVENTS + " JOIN " + Tables.CALENDARS
692                + " ON (" + Tables.EVENTS + "." + Calendar.Events.CALENDAR_ID
693                + "=" + Tables.CALENDARS + "." + Calendar.Calendars._ID
694                + ")";
695
696        db.execSQL("CREATE VIEW " + Views.EVENTS + " AS " + eventsSelect);
697    }
698}
699