CalendarContract.java revision 59dab49cb8350afd1baa77999ec2f91283d78536
1/*
2 * Copyright (C) 2006 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 android.provider;
18
19
20import android.accounts.Account;
21import android.app.AlarmManager;
22import android.app.PendingIntent;
23import android.content.ContentProviderClient;
24import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.CursorEntityIterator;
29import android.content.Entity;
30import android.content.EntityIterator;
31import android.content.Intent;
32import android.database.Cursor;
33import android.database.DatabaseUtils;
34import android.net.Uri;
35import android.os.RemoteException;
36import android.text.TextUtils;
37import android.text.format.DateUtils;
38import android.text.format.Time;
39import android.util.Log;
40
41/**
42 * <p>
43 * The contract between the calendar provider and applications. Contains
44 * definitions for the supported URIs and data columns.
45 * </p>
46 * <h3>Overview</h3>
47 * <p>
48 * CalendarContract defines the data model of calendar and event related
49 * information. This data is stored in a number of tables:
50 * </p>
51 * <ul>
52 * <li>The {@link Calendars} table holds the calendar specific information. Each
53 * row in this table contains the details for a single calendar, such as the
54 * name, color, sync info, etc.</li>
55 * <li>The {@link Events} table holds the event specific information. Each row
56 * in this table has the info for a single event. It contains information such
57 * as event title, location, start time, end time, etc. The event can occur
58 * one-time or can recur multiple times. Attendees, reminders, and extended
59 * properties are stored on separate tables and reference the {@link Events#_ID}
60 * to link them with the event.</li>
61 * <li>The {@link Instances} table holds the start and end time for occurrences
62 * of an event. Each row in this table represents a single occurrence. For
63 * one-time events there will be a 1:1 mapping of instances to events. For
64 * recurring events, multiple rows will automatically be generated which
65 * correspond to multiple occurrences of that event.</li>
66 * <li>The {@link Attendees} table holds the event attendee or guest
67 * information. Each row represents a single guest of an event. It specifies the
68 * type of guest they are and their attendance response for the event.</li>
69 * <li>The {@link Reminders} table holds the alert/notification data. Each row
70 * represents a single alert for an event. An event can have multiple reminders.
71 * The number of reminders per event is specified in
72 * {@link Calendars#MAX_REMINDERS} which is set by the Sync Adapter that owns
73 * the given calendar. Reminders are specified in minutes before the event and
74 * have a type.</li>
75 * <li>The {@link ExtendedProperties} table hold opaque data fields used by the
76 * sync adapter. The provider takes no action with items in this table except to
77 * delete them when their related events are deleted.</li>
78 * </ul>
79 * <p>
80 * Other tables include:
81 * </p>
82 * <ul>
83 * <li>
84 * {@link SyncState}, which contains free-form data maintained by the sync
85 * adapters</li>
86 * </ul>
87 *
88 * @hide
89 */
90public final class CalendarContract {
91    private static final String TAG = "Calendar";
92
93    /**
94     * Broadcast Action: This is the intent that gets fired when an alarm
95     * notification needs to be posted for a reminder.
96     */
97    public static final String EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
98
99    /**
100     * Intent Extras key: The start time of an event or an instance of a
101     * recurring event. (milliseconds since epoch)
102     */
103    public static final String EVENT_BEGIN_TIME = "beginTime";
104
105    /**
106     * Intent Extras key: The end time of an event or an instance of a recurring
107     * event. (milliseconds since epoch)
108     */
109    public static final String EVENT_END_TIME = "endTime";
110
111    /**
112     * This authority is used for writing to or querying from the calendar
113     * provider. Note: This is set at first run and cannot be changed without
114     * breaking apps that access the provider.
115     */
116    public static final String AUTHORITY = "com.android.calendar";
117
118    /**
119     * The content:// style URL for the top-level calendar authority
120     */
121    public static final Uri CONTENT_URI =
122        Uri.parse("content://" + AUTHORITY);
123
124    /**
125     * An optional insert, update or delete URI parameter that allows the caller
126     * to specify that it is a sync adapter. The default value is false. If set
127     * to true, the modified row is not marked as "dirty" (needs to be synced)
128     * and when the provider calls
129     * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
130     * , the third parameter "syncToNetwork" is set to false. Furthermore, if
131     * set to true, the caller must also include
132     * {@link Calendars#ACCOUNT_NAME} and {@link Calendars#ACCOUNT_TYPE} as
133     * query parameters.
134     *
135     * @see Uri.Builder#appendQueryParameter(java.lang.String, java.lang.String)
136     */
137    public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
138
139    /**
140     * A special account type for calendars not associated with any account.
141     * Normally calendars that do not match an account on the device will be
142     * removed. Setting the account_type on a calendar to this will prevent it
143     * from being wiped if it does not match an existing account.
144     *
145     * @see SyncColumns#ACCOUNT_TYPE
146     */
147    public static final String ACCOUNT_TYPE_LOCAL = "LOCAL";
148
149    /**
150     * Generic columns for use by sync adapters. The specific functions of these
151     * columns are private to the sync adapter. Other clients of the API should
152     * not attempt to either read or write this column. These columns are
153     * editable as part of the Calendars Uri, but can only be read if accessed
154     * through any other Uri.
155     */
156    protected interface CalendarSyncColumns {
157
158
159        /**
160         * Generic column for use by sync adapters. Column name.
161         * <P>Type: TEXT</P>
162         */
163        public static final String CAL_SYNC1 = "cal_sync1";
164
165        /**
166         * Generic column for use by sync adapters. Column name.
167         * <P>Type: TEXT</P>
168         */
169        public static final String CAL_SYNC2 = "cal_sync2";
170
171        /**
172         * Generic column for use by sync adapters. Column name.
173         * <P>Type: TEXT</P>
174         */
175        public static final String CAL_SYNC3 = "cal_sync3";
176
177        /**
178         * Generic column for use by sync adapters. Column name.
179         * <P>Type: TEXT</P>
180         */
181        public static final String CAL_SYNC4 = "cal_sync4";
182
183        /**
184         * Generic column for use by sync adapters. Column name.
185         * <P>Type: TEXT</P>
186         */
187        public static final String CAL_SYNC5 = "cal_sync5";
188
189        /**
190         * Generic column for use by sync adapters. Column name.
191         * <P>Type: TEXT</P>
192         */
193        public static final String CAL_SYNC6 = "cal_sync6";
194
195        /**
196         * Generic column for use by sync adapters. Column name.
197         * <P>Type: TEXT</P>
198         */
199        public static final String CAL_SYNC7 = "cal_sync7";
200
201        /**
202         * Generic column for use by sync adapters. Column name.
203         * <P>Type: TEXT</P>
204         */
205        public static final String CAL_SYNC8 = "cal_sync8";
206
207        /**
208         * Generic column for use by sync adapters. Column name.
209         * <P>Type: TEXT</P>
210         */
211        public static final String CAL_SYNC9 = "cal_sync9";
212
213        /**
214         * Generic column for use by sync adapters. Column name.
215         * <P>Type: TEXT</P>
216         */
217        public static final String CAL_SYNC10 = "cal_sync10";
218    }
219
220    /**
221     * Columns for Sync information used by Calendars and Events tables. These
222     * have specific uses which are expected to be consistent by the app and
223     * sync adapter.
224     *
225     */
226    protected interface SyncColumns extends CalendarSyncColumns {
227        /**
228         * The account that was used to sync the entry to the device. If the
229         * account_type is not {@link #ACCOUNT_TYPE_LOCAL} then the name and
230         * type must match an account on the device or the calendar will be
231         * deleted.
232         * <P>Type: TEXT</P>
233         */
234        public static final String ACCOUNT_NAME = "account_name";
235
236        /**
237         * The type of the account that was used to sync the entry to the
238         * device. A type of {@link #ACCOUNT_TYPE_LOCAL} will keep this event
239         * form being deleted if there are no matching accounts on the device.
240         * <P>Type: TEXT</P>
241         */
242        public static final String ACCOUNT_TYPE = "account_type";
243
244        /**
245         * The unique ID for a row assigned by the sync source. NULL if the row
246         * has never been synced. This is used as a reference id for exceptions
247         * along with {@link BaseColumns#_ID}.
248         * <P>Type: TEXT</P>
249         */
250        public static final String _SYNC_ID = "_sync_id";
251
252        /**
253         * Used to indicate that local, unsynced, changes are present.
254         * <P>Type: INTEGER (long)</P>
255         */
256        public static final String DIRTY = "dirty";
257
258        /**
259         * Whether the row has been deleted but not synced to the server. A
260         * deleted row should be ignored.
261         * <P>
262         * Type: INTEGER (boolean)
263         * </P>
264         */
265        public static final String DELETED = "deleted";
266
267        /**
268         * If set to 1 this causes events on this calendar to be duplicated with
269         * {@link Events#LAST_SYNCED} set to 1 whenever the event
270         * transitions from non-dirty to dirty. The duplicated event will not be
271         * expanded in the instances table and will only show up in sync adapter
272         * queries of the events table. It will also be deleted when the
273         * originating event has its dirty flag cleared by the sync adapter.
274         * <P>Type: INTEGER (boolean)</P>
275         */
276        public static final String CAN_PARTIALLY_UPDATE = "canPartiallyUpdate";
277    }
278
279    /**
280     * Columns specific to the Calendars Uri that other Uris can query.
281     */
282    protected interface CalendarsColumns {
283        /**
284         * The color of the calendar
285         * <P>Type: INTEGER (color value)</P>
286         */
287        public static final String CALENDAR_COLOR = "calendar_color";
288
289        /**
290         * The display name of the calendar. Column name.
291         * <P>Type: TEXT</P>
292         */
293        public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName";
294
295        /**
296         * The level of access that the user has for the calendar
297         * <P>Type: INTEGER (one of the values below)</P>
298         */
299        public static final String CALENDAR_ACCESS_LEVEL = "calendar_access_level";
300
301        /** Cannot access the calendar */
302        public static final int CAL_ACCESS_NONE = 0;
303        /** Can only see free/busy information about the calendar */
304        public static final int CAL_ACCESS_FREEBUSY = 100;
305        /** Can read all event details */
306        public static final int CAL_ACCESS_READ = 200;
307        /** Can reply yes/no/maybe to an event */
308        public static final int CAL_ACCESS_RESPOND = 300;
309        /** not used */
310        public static final int CAL_ACCESS_OVERRIDE = 400;
311        /** Full access to modify the calendar, but not the access control
312         * settings
313         */
314        public static final int CAL_ACCESS_CONTRIBUTOR = 500;
315        /** Full access to modify the calendar, but not the access control
316         * settings
317         */
318        public static final int CAL_ACCESS_EDITOR = 600;
319        /** Full access to the calendar */
320        public static final int CAL_ACCESS_OWNER = 700;
321        /** Domain admin */
322        public static final int CAL_ACCESS_ROOT = 800;
323
324        /**
325         * Is the calendar selected to be displayed?
326         * 0 - do not show events associated with this calendar.
327         * 1 - show events associated with this calendar
328         * <P>Type: INTEGER (boolean)</P>
329         */
330        public static final String VISIBLE = "visible";
331
332        /**
333         * The time zone the calendar is associated with.
334         * <P>Type: TEXT</P>
335         */
336        public static final String CALENDAR_TIME_ZONE = "calendar_timezone";
337
338        /**
339         * Is this calendar synced and are its events stored on the device?
340         * 0 - Do not sync this calendar or store events for this calendar.
341         * 1 - Sync down events for this calendar.
342         * <p>Type: INTEGER (boolean)</p>
343         */
344        public static final String SYNC_EVENTS = "sync_events";
345
346        /**
347         * The owner account for this calendar, based on the calendar feed.
348         * This will be different from the _SYNC_ACCOUNT for delegated calendars.
349         * Column name.
350         * <P>Type: String</P>
351         */
352        public static final String OWNER_ACCOUNT = "ownerAccount";
353
354        /**
355         * Can the organizer respond to the event?  If no, the status of the
356         * organizer should not be shown by the UI.  Defaults to 1. Column name.
357         * <P>Type: INTEGER (boolean)</P>
358         */
359        public static final String CAN_ORGANIZER_RESPOND = "canOrganizerRespond";
360
361        /**
362         * Can the organizer modify the time zone of the event? Column name.
363         * <P>Type: INTEGER (boolean)</P>
364        */
365        public static final String CAN_MODIFY_TIME_ZONE = "canModifyTimeZone";
366
367        /**
368         * The maximum number of reminders allowed for an event. Column name.
369         * <P>Type: INTEGER</P>
370         */
371        public static final String MAX_REMINDERS = "maxReminders";
372
373        /**
374         * A comma separated list of reminder methods supported for this
375         * calendar in the format "#,#,#". Valid types are
376         * {@link Reminders#METHOD_DEFAULT}, {@link Reminders#METHOD_ALERT},
377         * {@link Reminders#METHOD_EMAIL}, {@link Reminders#METHOD_SMS}. Column
378         * name.
379         * <P>Type: TEXT</P>
380         */
381        public static final String ALLOWED_REMINDERS = "allowedReminders";
382    }
383
384    /**
385     * Class that represents a Calendar Entity. There is one entry per calendar.
386     * This is a helper class to make batch operations easier.
387     */
388    public static class CalendarsEntity implements BaseColumns, SyncColumns, CalendarsColumns {
389
390        /**
391         * The default Uri used when creating a new calendar EntityIterator.
392         */
393        @SuppressWarnings("hiding")
394        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
395                "/calendar_entities");
396
397        /**
398         * Creates an entity iterator for the given cursor. It assumes the
399         * cursor contains a calendars query.
400         *
401         * @param cursor query on {@link #CONTENT_URI}
402         * @return an EntityIterator of calendars
403         */
404        public static EntityIterator newEntityIterator(Cursor cursor) {
405            return new EntityIteratorImpl(cursor);
406        }
407
408        private static class EntityIteratorImpl extends CursorEntityIterator {
409
410            public EntityIteratorImpl(Cursor cursor) {
411                super(cursor);
412            }
413
414            @Override
415            public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
416                // we expect the cursor is already at the row we need to read from
417                final long calendarId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
418
419                // Create the content value
420                ContentValues cv = new ContentValues();
421                cv.put(_ID, calendarId);
422
423                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
424                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
425
426                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
427                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
428
429                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
430                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
431                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
432                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
433                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
434                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
435                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
436                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
437                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
438                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
439
440                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
441                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
442                        Calendars.CALENDAR_DISPLAY_NAME);
443                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
444                        Calendars.CALENDAR_COLOR);
445                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ACCESS_LEVEL);
446                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
447                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
448                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
449                        Calendars.CALENDAR_LOCATION);
450                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CALENDAR_TIME_ZONE);
451                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
452                        Calendars.OWNER_ACCOUNT);
453                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
454                        Calendars.CAN_ORGANIZER_RESPOND);
455                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
456                        Calendars.CAN_MODIFY_TIME_ZONE);
457                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
458                        Calendars.MAX_REMINDERS);
459                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
460                        Calendars.CAN_PARTIALLY_UPDATE);
461                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
462                        Calendars.ALLOWED_REMINDERS);
463
464                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
465
466                // Create the Entity from the ContentValue
467                Entity entity = new Entity(cv);
468
469                // Set cursor to next row
470                cursor.moveToNext();
471
472                // Return the created Entity
473                return entity;
474            }
475        }
476     }
477
478    /**
479     * Fields and helpers for interacting with Calendars.
480     */
481    public static class Calendars implements BaseColumns, SyncColumns, CalendarsColumns {
482        private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars.ACCOUNT_NAME + "=?"
483                + " AND "
484                + Calendars.ACCOUNT_TYPE + "=?";
485
486        /**
487         * Helper function for generating a calendars query. This is blocking
488         * and should not be used on the UI thread. See
489         * {@link ContentResolver#query(Uri, String[], String, String[], String)}
490         * for more details about using the parameters.
491         *
492         * @param cr The ContentResolver to query with
493         * @param projection A list of columns to return
494         * @param selection A formatted selection string
495         * @param selectionArgs arguments to the selection string
496         * @param orderBy How to order the returned rows
497         * @return
498         */
499        public static final Cursor query(ContentResolver cr, String[] projection, String selection,
500                String[] selectionArgs, String orderBy) {
501            return cr.query(CONTENT_URI, projection, selection, selectionArgs,
502                    orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
503        }
504
505        /**
506         * Convenience method perform a delete on the Calendar provider. This is
507         * a blocking call and should not be used on the UI thread.
508         *
509         * @param cr the ContentResolver
510         * @param selection A filter to apply to rows before deleting, formatted
511         *            as an SQL WHERE clause (excluding the WHERE itself).
512         * @param selectionArgs Fill in the '?'s in the selection
513         * @return the count of rows that were deleted
514         */
515        public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
516        {
517            return cr.delete(CONTENT_URI, selection, selectionArgs);
518        }
519
520        /**
521         * Convenience method to delete all calendars that match the account.
522         * This is a blocking call and should not be used on the UI thread.
523         *
524         * @param cr the ContentResolver
525         * @param account the account whose calendars and events should be
526         *            deleted
527         * @return the count of calendar rows that were deleted
528         */
529        public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
530            // delete all calendars that match this account
531            return CalendarContract.Calendars.delete(cr,
532                    WHERE_DELETE_FOR_ACCOUNT,
533                    new String[] { account.name, account.type });
534        }
535
536        /**
537         * The content:// style URL for accessing Calendars
538         */
539        @SuppressWarnings("hiding")
540        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
541
542        /**
543         * The default sort order for this table
544         */
545        public static final String DEFAULT_SORT_ORDER = "displayName";
546
547        /**
548         * The name of the calendar. Column name.
549         * <P>Type: TEXT</P>
550         */
551        public static final String NAME = "name";
552
553        /**
554         * The default location for the calendar. Column name.
555         * <P>Type: TEXT</P>
556         */
557        public static final String CALENDAR_LOCATION = "calendar_location";
558
559        /**
560         * These fields are only writable by a sync adapter. To modify them the
561         * caller must include {@link #CALLER_IS_SYNCADAPTER},
562         * {@link #ACCOUNT_NAME}, and {@link #ACCOUNT_TYPE} in the Uri's query
563         * parameters.
564         */
565        public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
566            ACCOUNT_NAME,
567            ACCOUNT_TYPE,
568            _SYNC_ID,
569            DIRTY,
570            OWNER_ACCOUNT,
571            MAX_REMINDERS,
572            CAN_MODIFY_TIME_ZONE,
573            CAN_ORGANIZER_RESPOND,
574            CAN_PARTIALLY_UPDATE,
575            CALENDAR_LOCATION,
576            CALENDAR_TIME_ZONE,
577            CALENDAR_ACCESS_LEVEL,
578            DELETED,
579            CAL_SYNC1,
580            CAL_SYNC2,
581            CAL_SYNC3,
582            CAL_SYNC4,
583            CAL_SYNC5,
584            CAL_SYNC6,
585            CAL_SYNC7,
586            CAL_SYNC8,
587            CAL_SYNC9,
588            CAL_SYNC10,
589        };
590    }
591
592    /**
593     * Columns from the Attendees table that other tables join into themselves.
594     */
595    protected interface AttendeesColumns {
596
597        /**
598         * The id of the event. Column name.
599         * <P>Type: INTEGER</P>
600         */
601        public static final String EVENT_ID = "event_id";
602
603        /**
604         * The name of the attendee. Column name.
605         * <P>Type: STRING</P>
606         */
607        public static final String ATTENDEE_NAME = "attendeeName";
608
609        /**
610         * The email address of the attendee. Column name.
611         * <P>Type: STRING</P>
612         */
613        public static final String ATTENDEE_EMAIL = "attendeeEmail";
614
615        /**
616         * The relationship of the attendee to the user. Column name.
617         * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.</P>
618         */
619        public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
620
621        public static final int RELATIONSHIP_NONE = 0;
622        public static final int RELATIONSHIP_ATTENDEE = 1;
623        public static final int RELATIONSHIP_ORGANIZER = 2;
624        public static final int RELATIONSHIP_PERFORMER = 3;
625        public static final int RELATIONSHIP_SPEAKER = 4;
626
627        /**
628         * The type of attendee. Column name.
629         * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P>
630         */
631        public static final String ATTENDEE_TYPE = "attendeeType";
632
633        public static final int TYPE_NONE = 0;
634        public static final int TYPE_REQUIRED = 1;
635        public static final int TYPE_OPTIONAL = 2;
636
637        /**
638         * The attendance status of the attendee. Column name.
639         * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...).</P>
640         */
641        public static final String ATTENDEE_STATUS = "attendeeStatus";
642
643        public static final int ATTENDEE_STATUS_NONE = 0;
644        public static final int ATTENDEE_STATUS_ACCEPTED = 1;
645        public static final int ATTENDEE_STATUS_DECLINED = 2;
646        public static final int ATTENDEE_STATUS_INVITED = 3;
647        public static final int ATTENDEE_STATUS_TENTATIVE = 4;
648    }
649
650    /**
651     * Fields and helpers for interacting with Attendees. Each row of this table
652     * represents a single attendee or guest of an event. Calling
653     * {@link #query(ContentResolver, long)} will return a list of attendees for
654     * the event with the given eventId. Both apps and sync adapters may write
655     * to this table. There are six writable fields and all of them except
656     * {@link #ATTENDEE_NAME} must be included when inserting a new attendee.
657     * They are:
658     * <ul>
659     * <li>{@link #EVENT_ID}</li>
660     * <li>{@link #ATTENDEE_NAME}</li>
661     * <li>{@link #ATTENDEE_EMAIL}</li>
662     * <li>{@link #ATTENDEE_RELATIONSHIP}</li>
663     * <li>{@link #ATTENDEE_TYPE}</li>
664     * <li>{@link #ATTENDEE_STATUS}</li>
665     * </ul>
666     */
667    public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
668
669        /**
670         * The content:// style URL for accessing Attendees data
671         */
672        @SuppressWarnings("hiding")
673        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
674        /**
675         * the projection used by the attendees query
676         */
677        public static final String[] PROJECTION = new String[] {
678                _ID, ATTENDEE_NAME, ATTENDEE_EMAIL, ATTENDEE_RELATIONSHIP, ATTENDEE_STATUS,};
679        private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
680
681        /**
682         * Queries all attendees associated with the given event. This is a
683         * blocking call and should not be done on the UI thread.
684         *
685         * @param cr The content resolver to use for the query
686         * @param eventId The id of the event to retrieve attendees for
687         * @return A Cursor containing all attendees for the event
688         */
689        public static final Cursor query(ContentResolver cr, long eventId) {
690            String[] attArgs = {Long.toString(eventId)};
691            return cr.query(CONTENT_URI, PROJECTION, ATTENDEES_WHERE, attArgs /* selection args */,
692                    null /* sort order */);
693        }
694    }
695
696    /**
697     * Columns from the Events table that other tables join into themselves.
698     */
699    protected interface EventsColumns {
700
701        /**
702         * The {@link Calendars#_ID} of the calendar the event belongs to.
703         * Column name.
704         * <P>Type: INTEGER</P>
705         */
706        public static final String CALENDAR_ID = "calendar_id";
707
708        /**
709         * The title of the event. Column name.
710         * <P>Type: TEXT</P>
711         */
712        public static final String TITLE = "title";
713
714        /**
715         * The description of the event. Column name.
716         * <P>Type: TEXT</P>
717         */
718        public static final String DESCRIPTION = "description";
719
720        /**
721         * Where the event takes place. Column name.
722         * <P>Type: TEXT</P>
723         */
724        public static final String EVENT_LOCATION = "eventLocation";
725
726        /**
727         * A secondary color for the individual event. Column name.
728         * <P>Type: INTEGER</P>
729         */
730        public static final String EVENT_COLOR = "eventColor";
731
732        /**
733         * The event status. Column name.
734         * <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P>
735         */
736        public static final String STATUS = "eventStatus";
737
738        public static final int STATUS_TENTATIVE = 0;
739        public static final int STATUS_CONFIRMED = 1;
740        public static final int STATUS_CANCELED = 2;
741
742        /**
743         * This is a copy of the attendee status for the owner of this event.
744         * This field is copied here so that we can efficiently filter out
745         * events that are declined without having to look in the Attendees
746         * table. Column name.
747         *
748         * <P>Type: INTEGER (int)</P>
749         */
750        public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
751
752        /**
753         * This column is available for use by sync adapters. Column name.
754         * <P>Type: TEXT</P>
755         */
756        public static final String SYNC_DATA1 = "sync_data1";
757
758        /**
759         * This column is available for use by sync adapters. Column name.
760         * <P>Type: TEXT</P>
761         */
762        public static final String SYNC_DATA2 = "sync_data2";
763
764        /**
765         * This column is available for use by sync adapters. Column name.
766         * <P>Type: TEXT</P>
767         */
768        public static final String SYNC_DATA3 = "sync_data3";
769
770        /**
771         * This column is available for use by sync adapters. Column name.
772         * <P>Type: TEXT</P>
773         */
774        public static final String SYNC_DATA4 = "sync_data4";
775
776        /**
777         * This column is available for use by sync adapters. Column name.
778         * <P>Type: TEXT</P>
779         */
780        public static final String SYNC_DATA5 = "sync_data5";
781
782        /**
783         * This column is available for use by sync adapters. Column name.
784         * <P>Type: TEXT</P>
785         */
786        public static final String SYNC_DATA6 = "sync_data6";
787
788        /**
789         * This column is available for use by sync adapters. Column name.
790         * <P>Type: TEXT</P>
791         */
792        public static final String SYNC_DATA7 = "sync_data7";
793
794        /**
795         * This column is available for use by sync adapters. Column name.
796         * <P>Type: TEXT</P>
797         */
798        public static final String SYNC_DATA8 = "sync_data8";
799
800        /**
801         * This column is available for use by sync adapters. Column name.
802         * <P>Type: TEXT</P>
803         */
804        public static final String SYNC_DATA9 = "sync_data9";
805
806        /**
807         * This column is available for use by sync adapters. Column name.
808         * <P>Type: TEXT</P>
809         */
810        public static final String SYNC_DATA10 = "sync_data10";
811
812        /**
813         * Used to indicate that a row is not a real event but an original copy of a locally
814         * modified event. A copy is made when an event changes from non-dirty to dirty and the
815         * event is on a calendar with {@link Calendars#CAN_PARTIALLY_UPDATE} set to 1. This copy
816         * does not get expanded in the instances table and is only visible in queries made by a
817         * sync adapter. The copy gets removed when the event is changed back to non-dirty by a
818         * sync adapter.
819         * <P>Type: INTEGER (boolean)</P>
820         */
821        public static final String LAST_SYNCED = "lastSynced";
822
823        /**
824         * The time the event starts in UTC millis since epoch. Column name.
825         * <P>Type: INTEGER (long; millis since epoch)</P>
826         */
827        public static final String DTSTART = "dtstart";
828
829        /**
830         * The time the event ends in UTC millis since epoch. Column name.
831         * <P>Type: INTEGER (long; millis since epoch)</P>
832         */
833        public static final String DTEND = "dtend";
834
835        /**
836         * The duration of the event in RFC2445 format. Column name.
837         * <P>Type: TEXT (duration in RFC2445 format)</P>
838         */
839        public static final String DURATION = "duration";
840
841        /**
842         * The timezone for the event. Column name.
843         * <P>Type: TEXT</P>
844         */
845        public static final String EVENT_TIMEZONE = "eventTimezone";
846
847        /**
848         * The timezone for the end time of the event. Column name.
849         * <P>Type: TEXT</P>
850         */
851        public static final String EVENT_END_TIMEZONE = "eventEndTimezone";
852
853        /**
854         * Is the event all day (time zone independent). Column name.
855         * <P>Type: INTEGER (boolean)</P>
856         */
857        public static final String ALL_DAY = "allDay";
858
859        /**
860         * Defines how the event shows up for others when the calendar is
861         * shared. Column name.
862         * <P>Type: INTEGER (One of {@link #ACCESS_DEFAULT}, ...)</P>
863         */
864        public static final String ACCESS_LEVEL = "accessLevel";
865
866        /**
867         * Default access is controlled by the server and will be treated as
868         * public on the device.
869         */
870        public static final int ACCESS_DEFAULT = 0;
871        /**
872         * Confidential is not used by the app.
873         */
874        public static final int ACCESS_CONFIDENTIAL = 1;
875        /**
876         * Private shares the event as a free/busy slot with no details.
877         */
878        public static final int ACCESS_PRIVATE = 2;
879        /**
880         * Public makes the contents visible to anyone with access to the
881         * calendar.
882         */
883        public static final int ACCESS_PUBLIC = 3;
884
885        /**
886         * If this event counts as busy time or is still free time that can be
887         * scheduled over. Column name.
888         * <P>Type: INTEGER (One of {@link #AVAILABILITY_BUSY},
889         * {@link #AVAILABILITY_FREE})</P>
890         */
891        public static final String AVAILABILITY = "availability";
892
893        /**
894         * Indicates that this event takes up time and will conflict with other
895         * events.
896         */
897        public static final int AVAILABILITY_BUSY = 0;
898        /**
899         * Indicates that this event is free time and will not conflict with
900         * other events.
901         */
902        public static final int AVAILABILITY_FREE = 1;
903
904        /**
905         * Whether the event has an alarm or not. Column name.
906         * <P>Type: INTEGER (boolean)</P>
907         */
908        public static final String HAS_ALARM = "hasAlarm";
909
910        /**
911         * Whether the event has extended properties or not. Column name.
912         * <P>Type: INTEGER (boolean)</P>
913         */
914        public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
915
916        /**
917         * The recurrence rule for the event. Column name.
918         * <P>Type: TEXT</P>
919         */
920        public static final String RRULE = "rrule";
921
922        /**
923         * The recurrence dates for the event. Column name.
924         * <P>Type: TEXT</P>
925         */
926        public static final String RDATE = "rdate";
927
928        /**
929         * The recurrence exception rule for the event. Column name.
930         * <P>Type: TEXT</P>
931         */
932        public static final String EXRULE = "exrule";
933
934        /**
935         * The recurrence exception dates for the event. Column name.
936         * <P>Type: TEXT</P>
937         */
938        public static final String EXDATE = "exdate";
939
940        /**
941         * The {@link Events#_ID} of the original recurring event for which this
942         * event is an exception. Column name.
943         * <P>Type: TEXT</P>
944         */
945        public static final String ORIGINAL_ID = "original_id";
946
947        /**
948         * The _sync_id of the original recurring event for which this event is
949         * an exception. The provider should keep the original_id in sync when
950         * this is updated. Column name.
951         * <P>Type: TEXT</P>
952         */
953        public static final String ORIGINAL_SYNC_ID = "original_sync_id";
954
955        /**
956         * The original instance time of the recurring event for which this
957         * event is an exception. Column name.
958         * <P>Type: INTEGER (long; millis since epoch)</P>
959         */
960        public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
961
962        /**
963         * The allDay status (true or false) of the original recurring event
964         * for which this event is an exception. Column name.
965         * <P>Type: INTEGER (boolean)</P>
966         */
967        public static final String ORIGINAL_ALL_DAY = "originalAllDay";
968
969        /**
970         * The last date this event repeats on, or NULL if it never ends. Column
971         * name.
972         * <P>Type: INTEGER (long; millis since epoch)</P>
973         */
974        public static final String LAST_DATE = "lastDate";
975
976        /**
977         * Whether the event has attendee information.  True if the event
978         * has full attendee data, false if the event has information about
979         * self only. Column name.
980         * <P>Type: INTEGER (boolean)</P>
981         */
982        public static final String HAS_ATTENDEE_DATA = "hasAttendeeData";
983
984        /**
985         * Whether guests can modify the event. Column name.
986         * <P>Type: INTEGER (boolean)</P>
987         */
988        public static final String GUESTS_CAN_MODIFY = "guestsCanModify";
989
990        /**
991         * Whether guests can invite other guests. Column name.
992         * <P>Type: INTEGER (boolean)</P>
993         */
994        public static final String GUESTS_CAN_INVITE_OTHERS = "guestsCanInviteOthers";
995
996        /**
997         * Whether guests can see the list of attendees. Column name.
998         * <P>Type: INTEGER (boolean)</P>
999         */
1000        public static final String GUESTS_CAN_SEE_GUESTS = "guestsCanSeeGuests";
1001
1002        /**
1003         * Email of the organizer (owner) of the event. Column name.
1004         * <P>Type: STRING</P>
1005         */
1006        public static final String ORGANIZER = "organizer";
1007
1008        /**
1009         * Whether the user can invite others to the event. The
1010         * GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary
1011         * guest, while CAN_INVITE_OTHERS indicates if the user can invite
1012         * others (either through GUESTS_CAN_INVITE_OTHERS or because the user
1013         * has modify access to the event). Column name.
1014         * <P>Type: INTEGER (boolean, readonly)</P>
1015         */
1016        public static final String CAN_INVITE_OTHERS = "canInviteOthers";
1017    }
1018
1019    /**
1020     * Class that represents an Event Entity. There is one entry per event.
1021     * Recurring events show up as a single entry. This is a helper class to
1022     * make batch operations easier. A {@link ContentResolver} or
1023     * {@link ContentProviderClient} is required as the helper does additional
1024     * queries to add reminders and attendees to each entry.
1025     */
1026    public static final class EventsEntity implements BaseColumns, SyncColumns, EventsColumns {
1027        /**
1028         * The content:// style URL for this table
1029         */
1030        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1031                "/event_entities");
1032
1033        /**
1034         * Creates a new iterator for events
1035         *
1036         * @param cursor An event query
1037         * @param resolver For performing additional queries
1038         * @return an EntityIterator containing one entity per event in the
1039         *         cursor
1040         */
1041        public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
1042            return new EntityIteratorImpl(cursor, resolver);
1043        }
1044
1045        /**
1046         * Creates a new iterator for events
1047         *
1048         * @param cursor An event query
1049         * @param provider For performing additional queries
1050         * @return an EntityIterator containing one entity per event in the
1051         *         cursor
1052         */
1053        public static EntityIterator newEntityIterator(Cursor cursor,
1054                ContentProviderClient provider) {
1055            return new EntityIteratorImpl(cursor, provider);
1056        }
1057
1058        private static class EntityIteratorImpl extends CursorEntityIterator {
1059            private final ContentResolver mResolver;
1060            private final ContentProviderClient mProvider;
1061
1062            private static final String[] REMINDERS_PROJECTION = new String[] {
1063                    Reminders.MINUTES,
1064                    Reminders.METHOD,
1065            };
1066            private static final int COLUMN_MINUTES = 0;
1067            private static final int COLUMN_METHOD = 1;
1068
1069            private static final String[] ATTENDEES_PROJECTION = new String[] {
1070                    Attendees.ATTENDEE_NAME,
1071                    Attendees.ATTENDEE_EMAIL,
1072                    Attendees.ATTENDEE_RELATIONSHIP,
1073                    Attendees.ATTENDEE_TYPE,
1074                    Attendees.ATTENDEE_STATUS,
1075            };
1076            private static final int COLUMN_ATTENDEE_NAME = 0;
1077            private static final int COLUMN_ATTENDEE_EMAIL = 1;
1078            private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
1079            private static final int COLUMN_ATTENDEE_TYPE = 3;
1080            private static final int COLUMN_ATTENDEE_STATUS = 4;
1081            private static final String[] EXTENDED_PROJECTION = new String[] {
1082                    ExtendedProperties._ID,
1083                    ExtendedProperties.NAME,
1084                    ExtendedProperties.VALUE
1085            };
1086            private static final int COLUMN_ID = 0;
1087            private static final int COLUMN_NAME = 1;
1088            private static final int COLUMN_VALUE = 2;
1089
1090            private static final String WHERE_EVENT_ID = "event_id=?";
1091
1092            public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
1093                super(cursor);
1094                mResolver = resolver;
1095                mProvider = null;
1096            }
1097
1098            public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
1099                super(cursor);
1100                mResolver = null;
1101                mProvider = provider;
1102            }
1103
1104            @Override
1105            public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
1106                // we expect the cursor is already at the row we need to read from
1107                final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
1108                ContentValues cv = new ContentValues();
1109                cv.put(Events._ID, eventId);
1110                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
1111                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
1112                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
1113                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
1114                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
1115                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
1116                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
1117                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
1118                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
1119                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
1120                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_END_TIMEZONE);
1121                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
1122                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
1123                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
1124                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
1125                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
1126                        HAS_EXTENDED_PROPERTIES);
1127                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
1128                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
1129                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
1130                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
1131                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_SYNC_ID);
1132                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_ID);
1133                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
1134                        ORIGINAL_INSTANCE_TIME);
1135                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
1136                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
1137                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
1138                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
1139                        GUESTS_CAN_INVITE_OTHERS);
1140                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
1141                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
1142                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
1143                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
1144                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
1145                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
1146                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
1147                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA1);
1148                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA2);
1149                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA3);
1150                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA4);
1151                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA5);
1152                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA6);
1153                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA7);
1154                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA8);
1155                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA9);
1156                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA10);
1157                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
1158                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
1159                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
1160                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
1161                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
1162                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
1163                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
1164                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
1165                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
1166                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
1167
1168                Entity entity = new Entity(cv);
1169                Cursor subCursor;
1170                if (mResolver != null) {
1171                    subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
1172                            WHERE_EVENT_ID,
1173                            new String[] { Long.toString(eventId) }  /* selectionArgs */,
1174                            null /* sortOrder */);
1175                } else {
1176                    subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
1177                            WHERE_EVENT_ID,
1178                            new String[] { Long.toString(eventId) }  /* selectionArgs */,
1179                            null /* sortOrder */);
1180                }
1181                try {
1182                    while (subCursor.moveToNext()) {
1183                        ContentValues reminderValues = new ContentValues();
1184                        reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
1185                        reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
1186                        entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
1187                    }
1188                } finally {
1189                    subCursor.close();
1190                }
1191
1192                if (mResolver != null) {
1193                    subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
1194                            WHERE_EVENT_ID,
1195                            new String[] { Long.toString(eventId) } /* selectionArgs */,
1196                            null /* sortOrder */);
1197                } else {
1198                    subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
1199                            WHERE_EVENT_ID,
1200                            new String[] { Long.toString(eventId) } /* selectionArgs */,
1201                            null /* sortOrder */);
1202                }
1203                try {
1204                    while (subCursor.moveToNext()) {
1205                        ContentValues attendeeValues = new ContentValues();
1206                        attendeeValues.put(Attendees.ATTENDEE_NAME,
1207                                subCursor.getString(COLUMN_ATTENDEE_NAME));
1208                        attendeeValues.put(Attendees.ATTENDEE_EMAIL,
1209                                subCursor.getString(COLUMN_ATTENDEE_EMAIL));
1210                        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
1211                                subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
1212                        attendeeValues.put(Attendees.ATTENDEE_TYPE,
1213                                subCursor.getInt(COLUMN_ATTENDEE_TYPE));
1214                        attendeeValues.put(Attendees.ATTENDEE_STATUS,
1215                                subCursor.getInt(COLUMN_ATTENDEE_STATUS));
1216                        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
1217                    }
1218                } finally {
1219                    subCursor.close();
1220                }
1221
1222                if (mResolver != null) {
1223                    subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
1224                            WHERE_EVENT_ID,
1225                            new String[] { Long.toString(eventId) } /* selectionArgs */,
1226                            null /* sortOrder */);
1227                } else {
1228                    subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
1229                            WHERE_EVENT_ID,
1230                            new String[] { Long.toString(eventId) } /* selectionArgs */,
1231                            null /* sortOrder */);
1232                }
1233                try {
1234                    while (subCursor.moveToNext()) {
1235                        ContentValues extendedValues = new ContentValues();
1236                        extendedValues.put(ExtendedProperties._ID,
1237                                subCursor.getString(COLUMN_ID));
1238                        extendedValues.put(ExtendedProperties.NAME,
1239                                subCursor.getString(COLUMN_NAME));
1240                        extendedValues.put(ExtendedProperties.VALUE,
1241                                subCursor.getString(COLUMN_VALUE));
1242                        entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
1243                    }
1244                } finally {
1245                    subCursor.close();
1246                }
1247
1248                cursor.moveToNext();
1249                return entity;
1250            }
1251        }
1252    }
1253
1254    /**
1255     * Constants and helpers for the Events table, which contains details of a
1256     * single event. <h3>Operations</h3> All operations can be done either as an
1257     * app or as a sync adapter. To perform an operation as a sync adapter
1258     * {@link #CALLER_IS_SYNCADAPTER} should be set to true in the Uri
1259     * parameters and {@link #ACCOUNT_NAME} and {@link #ACCOUNT_TYPE} must be
1260     * set. Sync adapters have write access to more columns but are restricted
1261     * to a single account at a time.
1262     * <dl>
1263     * <dt><b>Insert</b></dt>
1264     * <dd>When inserting a new event the following fields must be included:
1265     * <ul>
1266     * <li>dtstart</li>
1267     * <li>dtend -or- a (rrule or rdate) and a duration</li>
1268     * <li>a calendar_id</li>
1269     * </ul>
1270     * There are also further requirements when inserting or updating an event.
1271     * See the section on Writing to Events.</dd>
1272     * <dt><b>Update</b></dt>
1273     * <dd>To perform an update on an Event the {@link Events#_ID} of the event
1274     * must be provided either as an appended id to the Uri (
1275     * {@link ContentUris#withAppendedId}) or as the first selection item--the
1276     * selection should start with "_id=?" and the first selectionArg should be
1277     * the _id of the event. Updating an event must respect the same rules as
1278     * inserting and is further restricted in the fields that can be written.
1279     * See the section on Writing to Events.</dd>
1280     * <dt><b>Delete</b></dt>
1281     * <dd>Events can be deleted either by the {@link Events#_ID} as an appended
1282     * id on the Uri or using any standard selection. If an appended id is used
1283     * a selection is not allowed. There are two versions of delete: as an app
1284     * and as a sync adapter. An app delete will set the deleted column on an
1285     * event and remove all instances of that event. A sync adapter delete will
1286     * remove the event from the database and all associated data.</dd>
1287     * <dt><b>Query</b></dt>
1288     * <dd>Querying the Events table will get you all information about a set of
1289     * events except their reminders, attendees, and extended properties. There
1290     * will be one row returned for each event that matches the query selection,
1291     * or at most a single row if the {@link Events#_ID} is appended to the Uri.
1292     * Recurring events will only return a single row regardless of the number
1293     * of times that event repeats.</dd>
1294     * </dl>
1295     * <h3>Writing to Events</h3> There are further restrictions on all Updates
1296     * and Inserts in the Events table:
1297     * <ul>
1298     * <li>If allDay is set to 1 eventTimezone must be {@link Time#TIMEZONE_UTC}
1299     * and the time must correspond to a midnight boundary.</li>
1300     * <li>Exceptions are not allowed to recur. If rrule or rdate is not empty,
1301     * original_id and original_sync_id must be empty.</li>
1302     * <li>In general a calendar_id should not be modified after insertion. This
1303     * is not explicitly forbidden but many sync adapters will not behave in an
1304     * expected way if the calendar_id is modified.</li>
1305     * </ul>
1306     * The following Events columns are writable by both an app and a sync
1307     * adapter.
1308     * <ul>
1309     * <li>{@link #CALENDAR_ID}</li>
1310     * <li>{@link #ORGANIZER}</li>
1311     * <li>{@link #TITLE}</li>
1312     * <li>{@link #EVENT_LOCATION}</li>
1313     * <li>{@link #DESCRIPTION}</li>
1314     * <li>{@link #EVENT_COLOR}</li>
1315     * <li>{@link #DTSTART}</li>
1316     * <li>{@link #DTEND}</li>
1317     * <li>{@link #EVENT_TIMEZONE}</li>
1318     * <li>{@link #EVENT_END_TIMEZONE}</li>
1319     * <li>{@link #DURATION}</li>
1320     * <li>{@link #ALL_DAY}</li>
1321     * <li>{@link #RRULE}</li>
1322     * <li>{@link #RDATE}</li>
1323     * <li>{@link #EXRULE}</li>
1324     * <li>{@link #EXDATE}</li>
1325     * <li>{@link #ORIGINAL_ID}</li>
1326     * <li>{@link #ORIGINAL_SYNC_ID}</li>
1327     * <li>{@link #ORIGINAL_INSTANCE_TIME}</li>
1328     * <li>{@link #ORIGINAL_ALL_DAY}</li>
1329     * <li>{@link #ACCESS_LEVEL}</li>
1330     * <li>{@link #AVAILABILITY}</li>
1331     * <li>{@link #GUESTS_CAN_MODIFY}</li>
1332     * <li>{@link #GUESTS_CAN_INVITE_OTHERS}</li>
1333     * <li>{@link #GUESTS_CAN_SEE_GUESTS}</li>
1334     * </ul>
1335     * The following Events columns are writable only by a sync adapter
1336     * <ul>
1337     * <li>{@link #DIRTY}</li>
1338     * <li>{@link #_SYNC_ID}</li>
1339     * <li>{@link #SYNC_DATA1}</li>
1340     * <li>{@link #SYNC_DATA2}</li>
1341     * <li>{@link #SYNC_DATA3}</li>
1342     * <li>{@link #SYNC_DATA4}</li>
1343     * <li>{@link #SYNC_DATA5}</li>
1344     * <li>{@link #SYNC_DATA6}</li>
1345     * <li>{@link #SYNC_DATA7}</li>
1346     * <li>{@link #SYNC_DATA8}</li>
1347     * <li>{@link #SYNC_DATA9}</li>
1348     * <li>{@link #SYNC_DATA10}</li>
1349     * </ul>
1350     * The remaining columns are either updated by the provider only or are
1351     * views into other tables and cannot be changed through the Events table.
1352     */
1353    public static final class Events implements BaseColumns, SyncColumns, EventsColumns,
1354            CalendarsColumns {
1355
1356        /**
1357         * Queries all events with the given projection. This is a blocking call
1358         * and should not be done on the UI thread.
1359         *
1360         * @param cr The content resolver to use for the query
1361         * @param projection The columns to return
1362         * @return A Cursor containing all events in the db
1363         */
1364        public static final Cursor query(ContentResolver cr, String[] projection) {
1365            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
1366        }
1367
1368        /**
1369         * Queries events using the given projection, selection filter, and
1370         * ordering. This is a blocking call and should not be done on the UI
1371         * thread. For selection and selectionArgs usage see
1372         * {@link ContentResolver#query(Uri, String[], String, String[], String)}
1373         *
1374         * @param cr The content resolver to use for the query
1375         * @param projection The columns to return
1376         * @param selection Filter on the query as an SQL WHERE statement
1377         * @param selectionArgs Args to replace any '?'s in the selection
1378         * @param orderBy How to order the rows as an SQL ORDER BY statement
1379         * @return A Cursor containing the matching events
1380         */
1381        public static final Cursor query(ContentResolver cr, String[] projection, String selection,
1382                String[] selectionArgs, String orderBy) {
1383            return cr.query(CONTENT_URI, projection, selection, null,
1384                    orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
1385        }
1386
1387        /**
1388         * The content:// style URL for interacting with events. Appending an
1389         * event id using {@link ContentUris#withAppendedId(Uri, long)} will
1390         * specify a single event.
1391         */
1392        @SuppressWarnings("hiding")
1393        public static final Uri CONTENT_URI =
1394                Uri.parse("content://" + AUTHORITY + "/events");
1395
1396        /**
1397         * The content:// style URI for recurring event exceptions.  Insertions require an
1398         * appended event ID.  Deletion of exceptions requires both the original event ID and
1399         * the exception event ID (see {@link Uri.Builder#appendPath}).
1400         */
1401        public static final Uri EXCEPTION_CONTENT_URI =
1402                Uri.parse("content://" + AUTHORITY + "/exception");
1403
1404        /**
1405         * The default sort order for this table
1406         */
1407        private static final String DEFAULT_SORT_ORDER = "";
1408
1409        /**
1410         * These are columns that should only ever be updated by the provider,
1411         * either because they are views mapped to another table or because they
1412         * are used for provider only functionality.
1413         */
1414        public static String[] PROVIDER_WRITABLE_COLUMNS = new String[] {
1415                ACCOUNT_NAME,
1416                ACCOUNT_TYPE,
1417                CAL_SYNC1,
1418                CAL_SYNC2,
1419                CAL_SYNC3,
1420                CAL_SYNC4,
1421                CAL_SYNC5,
1422                CAL_SYNC6,
1423                CAL_SYNC7,
1424                CAL_SYNC8,
1425                CAL_SYNC9,
1426                CAL_SYNC10,
1427                ALLOWED_REMINDERS,
1428                CALENDAR_ACCESS_LEVEL,
1429                CALENDAR_COLOR,
1430                CALENDAR_TIME_ZONE,
1431                CAN_MODIFY_TIME_ZONE,
1432                CAN_ORGANIZER_RESPOND,
1433                CALENDAR_DISPLAY_NAME,
1434                CAN_PARTIALLY_UPDATE,
1435                SYNC_EVENTS,
1436                VISIBLE,
1437        };
1438
1439        /**
1440         * These fields are only writable by a sync adapter. To modify them the
1441         * caller must include CALLER_IS_SYNCADAPTER, _SYNC_ACCOUNT, and
1442         * _SYNC_ACCOUNT_TYPE in the query parameters.
1443         */
1444        public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
1445            _SYNC_ID,
1446            DIRTY,
1447            SYNC_DATA1,
1448            SYNC_DATA2,
1449            SYNC_DATA3,
1450            SYNC_DATA4,
1451            SYNC_DATA5,
1452            SYNC_DATA6,
1453            SYNC_DATA7,
1454            SYNC_DATA8,
1455            SYNC_DATA9,
1456            SYNC_DATA10,
1457        };
1458    }
1459
1460    /**
1461     * Fields and helpers for interacting with Instances. An instance is a
1462     * single occurrence of an event including time zone specific start and end
1463     * days and minutes. The instances table is not writable and only provides a
1464     * way to query event occurrences.
1465     */
1466    public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
1467
1468        private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=1";
1469
1470        /**
1471         * Performs a query to return all visible instances in the given range.
1472         * This is a blocking function and should not be done on the UI thread.
1473         * This will cause an expansion of recurring events to fill this time
1474         * range if they are not already expanded and will slow down for larger
1475         * time ranges with many recurring events.
1476         *
1477         * @param cr The ContentResolver to use for the query
1478         * @param projection The columns to return
1479         * @param begin The start of the time range to query in UTC millis since
1480         *            epoch
1481         * @param end The end of the time range to query in UTC millis since
1482         *            epoch
1483         * @return A Cursor containing all instances in the given range
1484         */
1485        public static final Cursor query(ContentResolver cr, String[] projection,
1486                                         long begin, long end) {
1487            Uri.Builder builder = CONTENT_URI.buildUpon();
1488            ContentUris.appendId(builder, begin);
1489            ContentUris.appendId(builder, end);
1490            return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
1491                         null, DEFAULT_SORT_ORDER);
1492        }
1493
1494        /**
1495         * Performs a query to return all visible instances in the given range
1496         * that match the given query. This is a blocking function and should
1497         * not be done on the UI thread. This will cause an expansion of
1498         * recurring events to fill this time range if they are not already
1499         * expanded and will slow down for larger time ranges with many
1500         * recurring events.
1501         *
1502         * @param cr The ContentResolver to use for the query
1503         * @param projection The columns to return
1504         * @param begin The start of the time range to query in UTC millis since
1505         *            epoch
1506         * @param end The end of the time range to query in UTC millis since
1507         *            epoch
1508         * @param searchQuery A string of space separated search terms. Segments
1509         *            enclosed by double quotes will be treated as a single
1510         *            term.
1511         * @return A Cursor of instances matching the search terms in the given
1512         *         time range
1513         */
1514        public static final Cursor query(ContentResolver cr, String[] projection,
1515                                         long begin, long end, String searchQuery) {
1516            Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
1517            ContentUris.appendId(builder, begin);
1518            ContentUris.appendId(builder, end);
1519            builder = builder.appendPath(searchQuery);
1520            return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED, null,
1521                    DEFAULT_SORT_ORDER);
1522        }
1523
1524        /**
1525         * Performs a query to return all visible instances in the given range
1526         * that match the given selection. This is a blocking function and
1527         * should not be done on the UI thread. This will cause an expansion of
1528         * recurring events to fill this time range if they are not already
1529         * expanded and will slow down for larger time ranges with many
1530         * recurring events.
1531         *
1532         * @param cr The ContentResolver to use for the query
1533         * @param projection The columns to return
1534         * @param begin The start of the time range to query in UTC millis since
1535         *            epoch
1536         * @param end The end of the time range to query in UTC millis since
1537         *            epoch
1538         * @param selection Filter on the query as an SQL WHERE statement
1539         * @param selectionArgs Args to replace any '?'s in the selection
1540         * @param orderBy How to order the rows as an SQL ORDER BY statement
1541         * @return A Cursor of instances matching the selection
1542         */
1543        public static final Cursor query(ContentResolver cr, String[] projection, long begin,
1544                long end, String selection, String[] selectionArgs, String orderBy) {
1545            Uri.Builder builder = CONTENT_URI.buildUpon();
1546            ContentUris.appendId(builder, begin);
1547            ContentUris.appendId(builder, end);
1548            if (TextUtils.isEmpty(selection)) {
1549                selection = WHERE_CALENDARS_SELECTED;
1550            } else {
1551                selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
1552            }
1553            return cr.query(builder.build(), projection, selection, selectionArgs,
1554                    orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
1555        }
1556
1557        /**
1558         * Performs a query to return all visible instances in the given range
1559         * that match the given selection. This is a blocking function and
1560         * should not be done on the UI thread. This will cause an expansion of
1561         * recurring events to fill this time range if they are not already
1562         * expanded and will slow down for larger time ranges with many
1563         * recurring events.
1564         *
1565         * @param cr The ContentResolver to use for the query
1566         * @param projection The columns to return
1567         * @param begin The start of the time range to query in UTC millis since
1568         *            epoch
1569         * @param end The end of the time range to query in UTC millis since
1570         *            epoch
1571         * @param searchQuery A string of space separated search terms. Segments
1572         *            enclosed by double quotes will be treated as a single
1573         *            term.
1574         * @param selection Filter on the query as an SQL WHERE statement
1575         * @param selectionArgs Args to replace any '?'s in the selection
1576         * @param orderBy How to order the rows as an SQL ORDER BY statement
1577         * @return A Cursor of instances matching the selection
1578         */
1579        public static final Cursor query(ContentResolver cr, String[] projection, long begin,
1580                long end, String searchQuery, String selection, String[] selectionArgs,
1581                String orderBy) {
1582            Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
1583            ContentUris.appendId(builder, begin);
1584            ContentUris.appendId(builder, end);
1585            builder = builder.appendPath(searchQuery);
1586            if (TextUtils.isEmpty(selection)) {
1587                selection = WHERE_CALENDARS_SELECTED;
1588            } else {
1589                selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
1590            }
1591            return cr.query(builder.build(), projection, selection, selectionArgs,
1592                    orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
1593        }
1594
1595        /**
1596         * The content:// style URL for querying an instance range. The begin
1597         * and end of the range to query should be added as path segments if
1598         * this is used directly.
1599         */
1600        @SuppressWarnings("hiding")
1601        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1602                "/instances/when");
1603        /**
1604         * The content:// style URL for querying an instance range by Julian
1605         * Day. The start and end day should be added as path segments if this
1606         * is used directly.
1607         */
1608        public static final Uri CONTENT_BY_DAY_URI =
1609            Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
1610        /**
1611         * The content:// style URL for querying an instance range with a search
1612         * term. The begin, end, and search string should be appended as path
1613         * segments if this is used directly.
1614         */
1615        public static final Uri CONTENT_SEARCH_URI = Uri.parse("content://" + AUTHORITY +
1616                "/instances/search");
1617        /**
1618         * The content:// style URL for querying an instance range with a search
1619         * term. The start day, end day, and search string should be appended as
1620         * path segments if this is used directly.
1621         */
1622        public static final Uri CONTENT_SEARCH_BY_DAY_URI =
1623            Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
1624
1625        /**
1626         * The default sort order for this table.
1627         */
1628        private static final String DEFAULT_SORT_ORDER = "begin ASC";
1629
1630        /**
1631         * The beginning time of the instance, in UTC milliseconds. Column name.
1632         * <P>Type: INTEGER (long; millis since epoch)</P>
1633         */
1634        public static final String BEGIN = "begin";
1635
1636        /**
1637         * The ending time of the instance, in UTC milliseconds. Column name.
1638         * <P>Type: INTEGER (long; millis since epoch)</P>
1639         */
1640        public static final String END = "end";
1641
1642        /**
1643         * The _id of the event for this instance. Column name.
1644         * <P>Type: INTEGER (long, foreign key to the Events table)</P>
1645         */
1646        public static final String EVENT_ID = "event_id";
1647
1648        /**
1649         * The Julian start day of the instance, relative to the local time
1650         * zone. Column name.
1651         * <P>Type: INTEGER (int)</P>
1652         */
1653        public static final String START_DAY = "startDay";
1654
1655        /**
1656         * The Julian end day of the instance, relative to the local time
1657         * zone. Column name.
1658         * <P>Type: INTEGER (int)</P>
1659         */
1660        public static final String END_DAY = "endDay";
1661
1662        /**
1663         * The start minute of the instance measured from midnight in the
1664         * local time zone. Column name.
1665         * <P>Type: INTEGER (int)</P>
1666         */
1667        public static final String START_MINUTE = "startMinute";
1668
1669        /**
1670         * The end minute of the instance measured from midnight in the
1671         * local time zone. Column name.
1672         * <P>Type: INTEGER (int)</P>
1673         */
1674        public static final String END_MINUTE = "endMinute";
1675    }
1676
1677    /**
1678     * CalendarCache stores some settings for calendar including the current
1679     * time zone for the instaces. These settings are stored using a key/value
1680     * scheme.
1681     */
1682    protected interface CalendarCacheColumns {
1683        /**
1684         * The key for the setting. Keys are defined in {@link CalendarCache}.
1685         */
1686        public static final String KEY = "key";
1687
1688        /**
1689         * The value of the given setting.
1690         */
1691        public static final String VALUE = "value";
1692    }
1693
1694    public static class CalendarCache implements CalendarCacheColumns {
1695        /**
1696         * The URI to use for retrieving the properties from the Calendar db.
1697         */
1698        public static final Uri URI =
1699                Uri.parse("content://" + AUTHORITY + "/properties");
1700        public static final String[] POJECTION = { KEY, VALUE };
1701
1702        /**
1703         * If updating a property, this must be provided as the selection. All
1704         * other selections will fail. For queries this field can be omitted to
1705         * retrieve all properties or used to query a single property. Valid
1706         * keys include {@link #TIMEZONE_KEY_TYPE},
1707         * {@link #TIMEZONE_KEY_INSTANCES}, and
1708         * {@link #TIMEZONE_KEY_INSTANCES_PREVIOUS}, though the last one can
1709         * only be read, not written.
1710         */
1711        public static final String WHERE = "key=?";
1712
1713        /**
1714         * They key for updating the use of auto/home time zones in Calendar.
1715         * Valid values are {@link #TIMEZONE_TYPE_AUTO} or
1716         * {@link #TIMEZONE_TYPE_HOME}.
1717         */
1718        public static final String TIMEZONE_KEY_TYPE = "timezoneType";
1719
1720        /**
1721         * The key for updating the time zone used by the provider when it
1722         * generates the instances table. This should only be written if the
1723         * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id
1724         * should be written to this field.
1725         */
1726        public static final String TIMEZONE_KEY_INSTANCES = "timezoneInstances";
1727
1728        /**
1729         * The key for reading the last time zone set by the user. This should
1730         * only be read by apps and it will be automatically updated whenever
1731         * {@link #TIMEZONE_KEY_INSTANCES} is updated with
1732         * {@link #TIMEZONE_TYPE_HOME} set.
1733         */
1734        public static final String TIMEZONE_KEY_INSTANCES_PREVIOUS = "timezoneInstancesPrevious";
1735
1736        /**
1737         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
1738         * should stay in sync with the device's time zone.
1739         */
1740        public static final String TIMEZONE_TYPE_AUTO = "auto";
1741
1742        /**
1743         * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
1744         * should use a fixed time zone set by the user.
1745         */
1746        public static final String TIMEZONE_TYPE_HOME = "home";
1747    }
1748
1749    /**
1750     * A few Calendar globals are needed in the CalendarProvider for expanding
1751     * the Instances table and these are all stored in the first (and only) row
1752     * of the CalendarMetaData table.
1753     *
1754     * @hide
1755     */
1756    protected interface CalendarMetaDataColumns {
1757        /**
1758         * The local timezone that was used for precomputing the fields
1759         * in the Instances table.
1760         */
1761        public static final String LOCAL_TIMEZONE = "localTimezone";
1762
1763        /**
1764         * The minimum time used in expanding the Instances table,
1765         * in UTC milliseconds.
1766         * <P>Type: INTEGER</P>
1767         */
1768        public static final String MIN_INSTANCE = "minInstance";
1769
1770        /**
1771         * The maximum time used in expanding the Instances table,
1772         * in UTC milliseconds.
1773         * <P>Type: INTEGER</P>
1774         */
1775        public static final String MAX_INSTANCE = "maxInstance";
1776
1777        /**
1778         * The minimum Julian day in the EventDays table.
1779         * <P>Type: INTEGER</P>
1780         */
1781        public static final String MIN_EVENTDAYS = "minEventDays";
1782
1783        /**
1784         * The maximum Julian day in the EventDays table.
1785         * <P>Type: INTEGER</P>
1786         */
1787        public static final String MAX_EVENTDAYS = "maxEventDays";
1788    }
1789
1790    /**
1791     * @hide
1792     */
1793    public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns {
1794    }
1795
1796    protected interface EventDaysColumns {
1797        /**
1798         * The Julian starting day number. Column name.
1799         * <P>Type: INTEGER (int)</P>
1800         */
1801        public static final String STARTDAY = "startDay";
1802        /**
1803         * The Julian ending day number. Column name.
1804         * <P>Type: INTEGER (int)</P>
1805         */
1806        public static final String ENDDAY = "endDay";
1807
1808    }
1809
1810    /**
1811     * Fields and helpers for querying for a list of days that contain events.
1812     */
1813    public static final class EventDays implements EventDaysColumns {
1814        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
1815                + "/instances/groupbyday");
1816
1817        /**
1818         * The projection used by the EventDays query.
1819         */
1820        public static final String[] PROJECTION = { STARTDAY, ENDDAY };
1821        private static final String SELECTION = "selected=1";
1822
1823        /**
1824         * Retrieves the days with events for the Julian days starting at
1825         * "startDay" for "numDays". It returns a cursor containing startday and
1826         * endday representing the max range of days for all events beginning on
1827         * each startday.This is a blocking function and should not be done on
1828         * the UI thread.
1829         *
1830         * @param cr the ContentResolver
1831         * @param startDay the first Julian day in the range
1832         * @param numDays the number of days to load (must be at least 1)
1833         * @return a database cursor containing a list of start and end days for
1834         *         events
1835         */
1836        public static final Cursor query(ContentResolver cr, int startDay, int numDays) {
1837            if (numDays < 1) {
1838                return null;
1839            }
1840            int endDay = startDay + numDays - 1;
1841            Uri.Builder builder = CONTENT_URI.buildUpon();
1842            ContentUris.appendId(builder, startDay);
1843            ContentUris.appendId(builder, endDay);
1844            return cr.query(builder.build(), PROJECTION, SELECTION,
1845                    null /* selection args */, STARTDAY);
1846        }
1847    }
1848
1849    protected interface RemindersColumns {
1850        /**
1851         * The event the reminder belongs to. Column name.
1852         * <P>Type: INTEGER (foreign key to the Events table)</P>
1853         */
1854        public static final String EVENT_ID = "event_id";
1855
1856        /**
1857         * The minutes prior to the event that the alarm should ring.  -1
1858         * specifies that we should use the default value for the system.
1859         * Column name.
1860         * <P>Type: INTEGER</P>
1861         */
1862        public static final String MINUTES = "minutes";
1863
1864        /**
1865         * Passing this as a minutes value will use the default reminder
1866         * minutes.
1867         */
1868        public static final int MINUTES_DEFAULT = -1;
1869
1870        /**
1871         * The alarm method, as set on the server. {@link #METHOD_DEFAULT},
1872         * {@link #METHOD_ALERT}, {@link #METHOD_EMAIL}, and {@link #METHOD_SMS}
1873         * are possible values; the device will only process
1874         * {@link #METHOD_DEFAULT} and {@link #METHOD_ALERT} reminders (the
1875         * other types are simply stored so we can send the same reminder info
1876         * back to the server when we make changes).
1877         */
1878        public static final String METHOD = "method";
1879
1880        public static final int METHOD_DEFAULT = 0;
1881        public static final int METHOD_ALERT = 1;
1882        public static final int METHOD_EMAIL = 2;
1883        public static final int METHOD_SMS = 3;
1884    }
1885
1886    /**
1887     * Fields and helpers for accessing reminders for an event. Each row of this
1888     * table represents a single reminder for an event. Calling
1889     * {@link #query(ContentResolver, long)} will return a list of reminders for
1890     * the event with the given eventId. Both apps and sync adapters may write
1891     * to this table. There are three writable fields and all of them must be
1892     * included when inserting a new reminder. They are:
1893     * <ul>
1894     * <li>{@link #EVENT_ID}</li>
1895     * <li>{@link #MINUTES}</li>
1896     * <li>{@link #METHOD}</li>
1897     * </ul>
1898     */
1899    public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
1900        private static final String REMINDERS_WHERE = CalendarContract.Reminders.EVENT_ID + "=?";
1901        /**
1902         * The projection used by the reminders query.
1903         */
1904        public static final String[] PROJECTION = new String[] {
1905                _ID, MINUTES, METHOD,};
1906        @SuppressWarnings("hiding")
1907        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
1908
1909        /**
1910         * Queries all reminders associated with the given event. This is a
1911         * blocking call and should not be done on the UI thread.
1912         *
1913         * @param cr The content resolver to use for the query
1914         * @param eventId The id of the event to retrieve reminders for
1915         * @return A Cursor containing all reminders for the event
1916         */
1917        public static final Cursor query(ContentResolver cr, long eventId) {
1918            String[] remArgs = {Long.toString(eventId)};
1919            return cr.query(CONTENT_URI, PROJECTION, REMINDERS_WHERE, remArgs /* selection args */,
1920                    null /* sort order */);
1921        }
1922    }
1923
1924    protected interface CalendarAlertsColumns {
1925        /**
1926         * The event that the alert belongs to. Column name.
1927         * <P>Type: INTEGER (foreign key to the Events table)</P>
1928         */
1929        public static final String EVENT_ID = "event_id";
1930
1931        /**
1932         * The start time of the event, in UTC. Column name.
1933         * <P>Type: INTEGER (long; millis since epoch)</P>
1934         */
1935        public static final String BEGIN = "begin";
1936
1937        /**
1938         * The end time of the event, in UTC. Column name.
1939         * <P>Type: INTEGER (long; millis since epoch)</P>
1940         */
1941        public static final String END = "end";
1942
1943        /**
1944         * The alarm time of the event, in UTC. Column name.
1945         * <P>Type: INTEGER (long; millis since epoch)</P>
1946         */
1947        public static final String ALARM_TIME = "alarmTime";
1948
1949        /**
1950         * The creation time of this database entry, in UTC.
1951         * Useful for debugging missed reminders. Column name.
1952         * <P>Type: INTEGER (long; millis since epoch)</P>
1953         */
1954        public static final String CREATION_TIME = "creationTime";
1955
1956        /**
1957         * The time that the alarm broadcast was received by the Calendar app,
1958         * in UTC. Useful for debugging missed reminders. Column name.
1959         * <P>Type: INTEGER (long; millis since epoch)</P>
1960         */
1961        public static final String RECEIVED_TIME = "receivedTime";
1962
1963        /**
1964         * The time that the notification was created by the Calendar app,
1965         * in UTC. Useful for debugging missed reminders. Column name.
1966         * <P>Type: INTEGER (long; millis since epoch)</P>
1967         */
1968        public static final String NOTIFY_TIME = "notifyTime";
1969
1970        /**
1971         * The state of this alert. It starts out as {@link #SCHEDULED}, then
1972         * when the alarm goes off, it changes to {@link #FIRED}, and then when
1973         * the user dismisses the alarm it changes to {@link #DISMISSED}. Column
1974         * name.
1975         * <P>Type: INTEGER</P>
1976         */
1977        public static final String STATE = "state";
1978
1979        public static final int SCHEDULED = 0;
1980        public static final int FIRED = 1;
1981        public static final int DISMISSED = 2;
1982
1983        /**
1984         * The number of minutes that this alarm precedes the start time. Column
1985         * name.
1986         * <P>Type: INTEGER</P>
1987         */
1988        public static final String MINUTES = "minutes";
1989
1990        /**
1991         * The default sort order for this alerts queries
1992         */
1993        public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
1994    }
1995
1996    /**
1997     * Fields and helpers for accessing calendar alerts information. These
1998     * fields are for tracking which alerts have been fired. Scheduled alarms
1999     * will generate an intent using {@link #EVENT_REMINDER_ACTION}. Apps that
2000     * receive this action may update the {@link #STATE} for the reminder when
2001     * they have finished handling it. Apps that have their notifications
2002     * disabled should not modify the table to ensure that they do not conflict
2003     * with another app that is generating a notification. In general, apps
2004     * should not need to write to this table directly except to update the
2005     * state of a reminder.
2006     */
2007    public static final class CalendarAlerts implements BaseColumns,
2008            CalendarAlertsColumns, EventsColumns, CalendarsColumns {
2009
2010        /**
2011         * @hide
2012         */
2013        public static final String TABLE_NAME = "CalendarAlerts";
2014        /**
2015         * The Uri for querying calendar alert information
2016         */
2017        @SuppressWarnings("hiding")
2018        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
2019                "/calendar_alerts");
2020
2021        private static final String WHERE_ALARM_EXISTS = EVENT_ID + "=?"
2022                + " AND " + BEGIN + "=?"
2023                + " AND " + ALARM_TIME + "=?";
2024
2025        private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?";
2026        private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC";
2027
2028        private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + SCHEDULED
2029                + " AND " + ALARM_TIME + "<?"
2030                + " AND " + ALARM_TIME + ">?"
2031                + " AND " + END + ">=?";
2032
2033        /**
2034         * This URI is for grouping the query results by event_id and begin
2035         * time.  This will return one result per instance of an event.  So
2036         * events with multiple alarms will appear just once, but multiple
2037         * instances of a repeating event will show up multiple times.
2038         */
2039        public static final Uri CONTENT_URI_BY_INSTANCE =
2040            Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
2041
2042        private static final boolean DEBUG = true;
2043
2044        /**
2045         * Helper for inserting an alarm time associated with an event
2046         *
2047         * @hide
2048         */
2049        public static final Uri insert(ContentResolver cr, long eventId,
2050                long begin, long end, long alarmTime, int minutes) {
2051            ContentValues values = new ContentValues();
2052            values.put(CalendarAlerts.EVENT_ID, eventId);
2053            values.put(CalendarAlerts.BEGIN, begin);
2054            values.put(CalendarAlerts.END, end);
2055            values.put(CalendarAlerts.ALARM_TIME, alarmTime);
2056            long currentTime = System.currentTimeMillis();
2057            values.put(CalendarAlerts.CREATION_TIME, currentTime);
2058            values.put(CalendarAlerts.RECEIVED_TIME, 0);
2059            values.put(CalendarAlerts.NOTIFY_TIME, 0);
2060            values.put(CalendarAlerts.STATE, SCHEDULED);
2061            values.put(CalendarAlerts.MINUTES, minutes);
2062            return cr.insert(CONTENT_URI, values);
2063        }
2064
2065        /**
2066         * Queries alerts info using the given projection, selection filter, and
2067         * ordering. This is a blocking call and should not be done on the UI
2068         * thread. For selection and selectionArgs usage see
2069         * {@link ContentResolver#query(Uri, String[], String, String[], String)}
2070         *
2071         * @param cr The content resolver to use for the query
2072         * @param projection The columns to return
2073         * @param selection Filter on the query as an SQL WHERE statement
2074         * @param selectionArgs Args to replace any '?'s in the selection
2075         * @param sortOrder How to order the rows as an SQL ORDER BY statement
2076         * @return A Cursor containing the matching alerts
2077         */
2078        public static final Cursor query(ContentResolver cr, String[] projection,
2079                String selection, String[] selectionArgs, String sortOrder) {
2080            return cr.query(CONTENT_URI, projection, selection, selectionArgs,
2081                    sortOrder);
2082        }
2083
2084        /**
2085         * Finds the next alarm after (or equal to) the given time and returns
2086         * the time of that alarm or -1 if no such alarm exists. This is a
2087         * blocking call and should not be done on the UI thread.
2088         *
2089         * @param cr the ContentResolver
2090         * @param millis the time in UTC milliseconds
2091         * @return the next alarm time greater than or equal to "millis", or -1
2092         *         if no such alarm exists.
2093         */
2094        public static final long findNextAlarmTime(ContentResolver cr, long millis) {
2095            String selection = ALARM_TIME + ">=" + millis;
2096            // TODO: construct an explicit SQL query so that we can add
2097            // "LIMIT 1" to the end and get just one result.
2098            String[] projection = new String[] { ALARM_TIME };
2099            Cursor cursor = query(cr, projection,
2100                    WHERE_FINDNEXTALARMTIME,
2101                    new String[] {
2102                        Long.toString(millis)
2103                    },
2104                    SORT_ORDER_ALARMTIME_ASC);
2105            long alarmTime = -1;
2106            try {
2107                if (cursor != null && cursor.moveToFirst()) {
2108                    alarmTime = cursor.getLong(0);
2109                }
2110            } finally {
2111                if (cursor != null) {
2112                    cursor.close();
2113                }
2114            }
2115            return alarmTime;
2116        }
2117
2118        /**
2119         * Searches the CalendarAlerts table for alarms that should have fired
2120         * but have not and then reschedules them.  This method can be called
2121         * at boot time to restore alarms that may have been lost due to a
2122         * phone reboot.
2123         *
2124         * @param cr the ContentResolver
2125         * @param context the Context
2126         * @param manager the AlarmManager
2127         */
2128        public static final void rescheduleMissedAlarms(ContentResolver cr,
2129                Context context, AlarmManager manager) {
2130            // Get all the alerts that have been scheduled but have not fired
2131            // and should have fired by now and are not too old.
2132            long now = System.currentTimeMillis();
2133            long ancient = now - DateUtils.DAY_IN_MILLIS;
2134            String[] projection = new String[] {
2135                    ALARM_TIME,
2136            };
2137
2138            // TODO: construct an explicit SQL query so that we can add
2139            // "GROUPBY" instead of doing a sort and de-dup
2140            Cursor cursor = CalendarAlerts.query(cr,
2141                    projection,
2142                    WHERE_RESCHEDULE_MISSED_ALARMS,
2143                    new String[] {
2144                        Long.toString(now),
2145                        Long.toString(ancient),
2146                        Long.toString(now)
2147                    },
2148                    SORT_ORDER_ALARMTIME_ASC);
2149            if (cursor == null) {
2150                return;
2151            }
2152
2153            if (DEBUG) {
2154                Log.d(TAG, "missed alarms found: " + cursor.getCount());
2155            }
2156
2157            try {
2158                long alarmTime = -1;
2159
2160                while (cursor.moveToNext()) {
2161                    long newAlarmTime = cursor.getLong(0);
2162                    if (alarmTime != newAlarmTime) {
2163                        if (DEBUG) {
2164                            Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
2165                        }
2166                        scheduleAlarm(context, manager, newAlarmTime);
2167                        alarmTime = newAlarmTime;
2168                    }
2169                }
2170            } finally {
2171                cursor.close();
2172            }
2173        }
2174
2175        /**
2176         * Schedules an alarm intent with the system AlarmManager that will
2177         * notify listeners when a reminder should be fired. The provider will
2178         * keep scheduled reminders up to date but apps may use this to
2179         * implement snooze functionality without modifying the reminders table.
2180         * Scheduled alarms will generate an intent using
2181         * {@link #EVENT_REMINDER_ACTION}.
2182         *
2183         * @param context A context for referencing system resources
2184         * @param manager The AlarmManager to use or null
2185         * @param alarmTime The time to fire the intent in UTC millis since
2186         *            epoch
2187         */
2188        public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
2189            if (DEBUG) {
2190                Time time = new Time();
2191                time.set(alarmTime);
2192                String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
2193                Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
2194            }
2195
2196            if (manager == null) {
2197                manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
2198            }
2199
2200            Intent intent = new Intent(EVENT_REMINDER_ACTION);
2201            intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
2202            intent.putExtra(ALARM_TIME, alarmTime);
2203            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
2204            manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
2205        }
2206
2207        /**
2208         * Searches for an entry in the CalendarAlerts table that matches
2209         * the given event id, begin time and alarm time.  If one is found
2210         * then this alarm already exists and this method returns true.
2211         *
2212         * @param cr the ContentResolver
2213         * @param eventId the event id to match
2214         * @param begin the start time of the event in UTC millis
2215         * @param alarmTime the alarm time of the event in UTC millis
2216         * @return true if there is already an alarm for the given event
2217         *   with the same start time and alarm time.
2218         */
2219        public static final boolean alarmExists(ContentResolver cr, long eventId,
2220                long begin, long alarmTime) {
2221            // TODO: construct an explicit SQL query so that we can add
2222            // "LIMIT 1" to the end and get just one result.
2223            String[] projection = new String[] { ALARM_TIME };
2224            Cursor cursor = query(cr,
2225                    projection,
2226                    WHERE_ALARM_EXISTS,
2227                    new String[] {
2228                        Long.toString(eventId),
2229                        Long.toString(begin),
2230                        Long.toString(alarmTime)
2231                    },
2232                    null);
2233            boolean found = false;
2234            try {
2235                if (cursor != null && cursor.getCount() > 0) {
2236                    found = true;
2237                }
2238            } finally {
2239                if (cursor != null) {
2240                    cursor.close();
2241                }
2242            }
2243            return found;
2244        }
2245    }
2246
2247    protected interface ExtendedPropertiesColumns {
2248        /**
2249         * The event the extended property belongs to. Column name.
2250         * <P>Type: INTEGER (foreign key to the Events table)</P>
2251         */
2252        public static final String EVENT_ID = "event_id";
2253
2254        /**
2255         * The name of the extended property.  This is a uri of the form
2256         * {scheme}#{local-name} convention. Column name.
2257         * <P>Type: TEXT</P>
2258         */
2259        public static final String NAME = "name";
2260
2261        /**
2262         * The value of the extended property. Column name.
2263         * <P>Type: TEXT</P>
2264         */
2265        public static final String VALUE = "value";
2266    }
2267
2268    /**
2269     * Fields for accessing the Extended Properties. This is a generic set of
2270     * name/value pairs for use by sync adapters or apps to add extra
2271     * information to events. There are three writable columns and all three
2272     * must be present when inserting a new value. They are:
2273     * <ul>
2274     * <li>{@link #EVENT_ID}</li>
2275     * <li>{@link #NAME}</li>
2276     * <li>{@link #VALUE}</li>
2277     * </ul>
2278     */
2279   public static final class ExtendedProperties implements BaseColumns,
2280            ExtendedPropertiesColumns, EventsColumns {
2281        public static final Uri CONTENT_URI =
2282                Uri.parse("content://" + AUTHORITY + "/extendedproperties");
2283
2284        // TODO: fill out this class when we actually start utilizing extendedproperties
2285        // in the calendar application.
2286   }
2287
2288    /**
2289     * A table provided for sync adapters to use for storing private sync state data.
2290     *
2291     * @see SyncStateContract
2292     */
2293    public static final class SyncState implements SyncStateContract.Columns {
2294        /**
2295         * This utility class cannot be instantiated
2296         */
2297        private SyncState() {}
2298
2299        private static final String CONTENT_DIRECTORY =
2300                SyncStateContract.Constants.CONTENT_DIRECTORY;
2301
2302        /**
2303         * The content:// style URI for this table
2304         */
2305        public static final Uri CONTENT_URI =
2306                Uri.withAppendedPath(CalendarContract.CONTENT_URI, CONTENT_DIRECTORY);
2307    }
2308
2309    /**
2310     * Columns from the EventsRawTimes table
2311     *
2312     * @hide
2313     */
2314    protected interface EventsRawTimesColumns {
2315        /**
2316         * The corresponding event id. Column name.
2317         * <P>Type: INTEGER (long)</P>
2318         */
2319        public static final String EVENT_ID = "event_id";
2320
2321        /**
2322         * The RFC2445 compliant time the event starts. Column name.
2323         * <P>Type: TEXT</P>
2324         */
2325        public static final String DTSTART_2445 = "dtstart2445";
2326
2327        /**
2328         * The RFC2445 compliant time the event ends. Column name.
2329         * <P>Type: TEXT</P>
2330         */
2331        public static final String DTEND_2445 = "dtend2445";
2332
2333        /**
2334         * The RFC2445 compliant original instance time of the recurring event
2335         * for which this event is an exception. Column name.
2336         * <P>Type: TEXT</P>
2337         */
2338        public static final String ORIGINAL_INSTANCE_TIME_2445 = "originalInstanceTime2445";
2339
2340        /**
2341         * The RFC2445 compliant last date this event repeats on, or NULL if it
2342         * never ends. Column name.
2343         * <P>Type: TEXT</P>
2344         */
2345        public static final String LAST_DATE_2445 = "lastDate2445";
2346    }
2347
2348    /**
2349     * @hide
2350     */
2351    public static final class EventsRawTimes implements BaseColumns, EventsRawTimesColumns {
2352    }
2353}
2354