• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.provider;
18 
19 import android.accounts.Account;
20 import android.app.AlarmManager;
21 import android.app.PendingIntent;
22 import android.content.ContentProviderClient;
23 import android.content.ContentResolver;
24 import android.content.ContentUris;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.CursorEntityIterator;
28 import android.content.Entity;
29 import android.content.EntityIterator;
30 import android.content.Intent;
31 import android.database.Cursor;
32 import android.database.DatabaseUtils;
33 import android.net.Uri;
34 import android.os.RemoteException;
35 import android.pim.ICalendar;
36 import android.text.TextUtils;
37 import android.text.format.DateUtils;
38 import android.text.format.Time;
39 import android.util.Log;
40 
41 /**
42  * The Calendar provider contains all calendar events.
43  *
44  * @hide
45  */
46 public final class Calendar {
47 
48     public static final String TAG = "Calendar";
49 
50     /**
51      * Broadcast Action: An event reminder.
52      */
53     public static final String EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
54 
55     /**
56      * These are the symbolic names for the keys used in the extra data
57      * passed in the intent for event reminders.
58      */
59     public static final String EVENT_BEGIN_TIME = "beginTime";
60     public static final String EVENT_END_TIME = "endTime";
61 
62     public static final String AUTHORITY = "com.android.calendar";
63 
64     /**
65      * The content:// style URL for the top-level calendar authority
66      */
67     public static final Uri CONTENT_URI =
68         Uri.parse("content://" + AUTHORITY);
69 
70     /**
71      * An optional insert, update or delete URI parameter that allows the caller
72      * to specify that it is a sync adapter. The default value is false. If true
73      * the dirty flag is not automatically set and the "syncToNetwork" parameter
74      * is set to false when calling
75      * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
76      */
77     public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
78 
79     /**
80      * Columns from the Calendars table that other tables join into themselves.
81      */
82     public interface CalendarsColumns
83     {
84         /**
85          * The color of the calendar
86          * <P>Type: INTEGER (color value)</P>
87          */
88         public static final String COLOR = "color";
89 
90         /**
91          * The level of access that the user has for the calendar
92          * <P>Type: INTEGER (one of the values below)</P>
93          */
94         public static final String ACCESS_LEVEL = "access_level";
95 
96         /** Cannot access the calendar */
97         public static final int NO_ACCESS = 0;
98         /** Can only see free/busy information about the calendar */
99         public static final int FREEBUSY_ACCESS = 100;
100         /** Can read all event details */
101         public static final int READ_ACCESS = 200;
102         public static final int RESPOND_ACCESS = 300;
103         public static final int OVERRIDE_ACCESS = 400;
104         /** Full access to modify the calendar, but not the access control settings */
105         public static final int CONTRIBUTOR_ACCESS = 500;
106         public static final int EDITOR_ACCESS = 600;
107         /** Full access to the calendar */
108         public static final int OWNER_ACCESS = 700;
109         /** Domain admin */
110         public static final int ROOT_ACCESS = 800;
111 
112         /**
113          * Is the calendar selected to be displayed?
114          * <P>Type: INTEGER (boolean)</P>
115          */
116         public static final String SELECTED = "selected";
117 
118         /**
119          * The timezone the calendar's events occurs in
120          * <P>Type: TEXT</P>
121          */
122         public static final String TIMEZONE = "timezone";
123 
124         /**
125          * If this calendar is in the list of calendars that are selected for
126          * syncing then "sync_events" is 1, otherwise 0.
127          * <p>Type: INTEGER (boolean)</p>
128          */
129         public static final String SYNC_EVENTS = "sync_events";
130 
131         /**
132          * Sync state data.
133          * <p>Type: String (blob)</p>
134          */
135         public static final String SYNC_STATE = "sync_state";
136 
137         /**
138          * The account that was used to sync the entry to the device.
139          * <P>Type: TEXT</P>
140          */
141         public static final String _SYNC_ACCOUNT = "_sync_account";
142 
143         /**
144          * The type of the account that was used to sync the entry to the device.
145          * <P>Type: TEXT</P>
146          */
147         public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
148 
149         /**
150          * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
151          * <P>Type: TEXT</P>
152          */
153         public static final String _SYNC_ID = "_sync_id";
154 
155         /**
156          * The last time, from the sync source's point of view, that this row has been synchronized.
157          * <P>Type: INTEGER (long)</P>
158          */
159         public static final String _SYNC_TIME = "_sync_time";
160 
161         /**
162          * The version of the row, as assigned by the server.
163          * <P>Type: TEXT</P>
164          */
165         public static final String _SYNC_VERSION = "_sync_version";
166 
167         /**
168          * For use by sync adapter at its discretion; not modified by CalendarProvider
169          * Note that this column was formerly named _SYNC_LOCAL_ID.  We are using it to avoid a
170          * schema change.
171          * TODO Replace this with something more general in the future.
172          * <P>Type: INTEGER (long)</P>
173          */
174         public static final String _SYNC_DATA = "_sync_local_id";
175 
176         /**
177          * Used only in persistent providers, and only during merging.
178          * <P>Type: INTEGER (long)</P>
179          */
180         public static final String _SYNC_MARK = "_sync_mark";
181 
182         /**
183          * Used to indicate that local, unsynced, changes are present.
184          * <P>Type: INTEGER (long)</P>
185          */
186         public static final String _SYNC_DIRTY = "_sync_dirty";
187 
188         /**
189          * The name of the account instance to which this row belongs, which when paired with
190          * {@link #ACCOUNT_TYPE} identifies a specific account.
191          * <P>Type: TEXT</P>
192          */
193         public static final String ACCOUNT_NAME = "account_name";
194 
195         /**
196          * The type of account to which this row belongs, which when paired with
197          * {@link #ACCOUNT_NAME} identifies a specific account.
198          * <P>Type: TEXT</P>
199          */
200         public static final String ACCOUNT_TYPE = "account_type";
201     }
202 
203     /**
204      * Contains a list of available calendars.
205      */
206     public static class Calendars implements BaseColumns, CalendarsColumns
207     {
208         private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?"
209                 + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
210 
query(ContentResolver cr, String[] projection, String where, String orderBy)211         public static final Cursor query(ContentResolver cr, String[] projection,
212                                        String where, String orderBy)
213         {
214             return cr.query(CONTENT_URI, projection, where,
215                                          null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
216         }
217 
218         /**
219          * Convenience method perform a delete on the Calendar provider
220          *
221          * @param cr the ContentResolver
222          * @param selection the rows to delete
223          * @return the count of rows that were deleted
224          */
delete(ContentResolver cr, String selection, String[] selectionArgs)225         public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
226         {
227             return cr.delete(CONTENT_URI, selection, selectionArgs);
228         }
229 
230         /**
231          * Convenience method to delete all calendars that match the account.
232          *
233          * @param cr the ContentResolver
234          * @param account the account whose rows should be deleted
235          * @return the count of rows that were deleted
236          */
deleteCalendarsForAccount(ContentResolver cr, Account account)237         public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
238             // delete all calendars that match this account
239             return Calendar.Calendars.delete(cr,
240                     WHERE_DELETE_FOR_ACCOUNT,
241                     new String[] { account.name, account.type });
242         }
243 
244         /**
245          * The content:// style URL for this table
246          */
247         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
248 
249         /**
250          * The default sort order for this table
251          */
252         public static final String DEFAULT_SORT_ORDER = "displayName";
253 
254         /**
255          * The URL to the calendar
256          * <P>Type: TEXT (URL)</P>
257          */
258         public static final String URL = "url";
259 
260         /**
261          * The name of the calendar
262          * <P>Type: TEXT</P>
263          */
264         public static final String NAME = "name";
265 
266         /**
267          * The display name of the calendar
268          * <P>Type: TEXT</P>
269          */
270         public static final String DISPLAY_NAME = "displayName";
271 
272         /**
273          * The location the of the events in the calendar
274          * <P>Type: TEXT</P>
275          */
276         public static final String LOCATION = "location";
277 
278         /**
279          * Should the calendar be hidden in the calendar selection panel?
280          * <P>Type: INTEGER (boolean)</P>
281          */
282         public static final String HIDDEN = "hidden";
283 
284         /**
285          * The owner account for this calendar, based on the calendar feed.
286          * This will be different from the _SYNC_ACCOUNT for delegated calendars.
287          * <P>Type: String</P>
288          */
289         public static final String OWNER_ACCOUNT = "ownerAccount";
290 
291         /**
292          * Can the organizer respond to the event?  If no, the status of the
293          * organizer should not be shown by the UI.  Defaults to 1
294          * <P>Type: INTEGER (boolean)</P>
295          */
296         public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
297     }
298 
299     public interface AttendeesColumns {
300 
301         /**
302          * The id of the event.
303          * <P>Type: INTEGER</P>
304          */
305         public static final String EVENT_ID = "event_id";
306 
307         /**
308          * The name of the attendee.
309          * <P>Type: STRING</P>
310          */
311         public static final String ATTENDEE_NAME = "attendeeName";
312 
313         /**
314          * The email address of the attendee.
315          * <P>Type: STRING</P>
316          */
317         public static final String ATTENDEE_EMAIL = "attendeeEmail";
318 
319         /**
320          * The relationship of the attendee to the user.
321          * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.
322          */
323         public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
324 
325         public static final int RELATIONSHIP_NONE = 0;
326         public static final int RELATIONSHIP_ATTENDEE = 1;
327         public static final int RELATIONSHIP_ORGANIZER = 2;
328         public static final int RELATIONSHIP_PERFORMER = 3;
329         public static final int RELATIONSHIP_SPEAKER = 4;
330 
331         /**
332          * The type of attendee.
333          * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})
334          */
335         public static final String ATTENDEE_TYPE = "attendeeType";
336 
337         public static final int TYPE_NONE = 0;
338         public static final int TYPE_REQUIRED = 1;
339         public static final int TYPE_OPTIONAL = 2;
340 
341         /**
342          * The attendance status of the attendee.
343          * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...}.
344          */
345         public static final String ATTENDEE_STATUS = "attendeeStatus";
346 
347         public static final int ATTENDEE_STATUS_NONE = 0;
348         public static final int ATTENDEE_STATUS_ACCEPTED = 1;
349         public static final int ATTENDEE_STATUS_DECLINED = 2;
350         public static final int ATTENDEE_STATUS_INVITED = 3;
351         public static final int ATTENDEE_STATUS_TENTATIVE = 4;
352     }
353 
354     public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
355         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
356 
357         // TODO: fill out this class when we actually start utilizing attendees
358         // in the calendar application.
359     }
360 
361     /**
362      * Columns from the Events table that other tables join into themselves.
363      */
364     public interface EventsColumns
365     {
366         /**
367          * The calendar the event belongs to
368          * <P>Type: INTEGER (foreign key to the Calendars table)</P>
369          */
370         public static final String CALENDAR_ID = "calendar_id";
371 
372         /**
373          * The URI for an HTML version of this event.
374          * <P>Type: TEXT</P>
375          */
376         public static final String HTML_URI = "htmlUri";
377 
378         /**
379          * The title of the event
380          * <P>Type: TEXT</P>
381          */
382         public static final String TITLE = "title";
383 
384         /**
385          * The description of the event
386          * <P>Type: TEXT</P>
387          */
388         public static final String DESCRIPTION = "description";
389 
390         /**
391          * Where the event takes place.
392          * <P>Type: TEXT</P>
393          */
394         public static final String EVENT_LOCATION = "eventLocation";
395 
396         /**
397          * The event status
398          * <P>Type: INTEGER (int)</P>
399          */
400         public static final String STATUS = "eventStatus";
401 
402         public static final int STATUS_TENTATIVE = 0;
403         public static final int STATUS_CONFIRMED = 1;
404         public static final int STATUS_CANCELED = 2;
405 
406         /**
407          * This is a copy of the attendee status for the owner of this event.
408          * This field is copied here so that we can efficiently filter out
409          * events that are declined without having to look in the Attendees
410          * table.
411          *
412          * <P>Type: INTEGER (int)</P>
413          */
414         public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
415 
416         /**
417          * This column is available for use by sync adapters
418          * <P>Type: TEXT</P>
419          */
420         public static final String SYNC_ADAPTER_DATA = "syncAdapterData";
421 
422         /**
423          * The comments feed uri.
424          * <P>Type: TEXT</P>
425          */
426         public static final String COMMENTS_URI = "commentsUri";
427 
428         /**
429          * The time the event starts
430          * <P>Type: INTEGER (long; millis since epoch)</P>
431          */
432         public static final String DTSTART = "dtstart";
433 
434         /**
435          * The time the event ends
436          * <P>Type: INTEGER (long; millis since epoch)</P>
437          */
438         public static final String DTEND = "dtend";
439 
440         /**
441          * The duration of the event
442          * <P>Type: TEXT (duration in RFC2445 format)</P>
443          */
444         public static final String DURATION = "duration";
445 
446         /**
447          * The timezone for the event.
448          * <P>Type: TEXT
449          */
450         public static final String EVENT_TIMEZONE = "eventTimezone";
451 
452         /**
453          * Whether the event lasts all day or not
454          * <P>Type: INTEGER (boolean)</P>
455          */
456         public static final String ALL_DAY = "allDay";
457 
458         /**
459          * Visibility for the event.
460          * <P>Type: INTEGER</P>
461          */
462         public static final String VISIBILITY = "visibility";
463 
464         public static final int VISIBILITY_DEFAULT = 0;
465         public static final int VISIBILITY_CONFIDENTIAL = 1;
466         public static final int VISIBILITY_PRIVATE = 2;
467         public static final int VISIBILITY_PUBLIC = 3;
468 
469         /**
470          * Transparency for the event -- does the event consume time on the calendar?
471          * <P>Type: INTEGER</P>
472          */
473         public static final String TRANSPARENCY = "transparency";
474 
475         public static final int TRANSPARENCY_OPAQUE = 0;
476 
477         public static final int TRANSPARENCY_TRANSPARENT = 1;
478 
479         /**
480          * Whether the event has an alarm or not
481          * <P>Type: INTEGER (boolean)</P>
482          */
483         public static final String HAS_ALARM = "hasAlarm";
484 
485         /**
486          * Whether the event has extended properties or not
487          * <P>Type: INTEGER (boolean)</P>
488          */
489         public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
490 
491         /**
492          * The recurrence rule for the event.
493          * than one.
494          * <P>Type: TEXT</P>
495          */
496         public static final String RRULE = "rrule";
497 
498         /**
499          * The recurrence dates for the event.
500          * <P>Type: TEXT</P>
501          */
502         public static final String RDATE = "rdate";
503 
504         /**
505          * The recurrence exception rule for the event.
506          * <P>Type: TEXT</P>
507          */
508         public static final String EXRULE = "exrule";
509 
510         /**
511          * The recurrence exception dates for the event.
512          * <P>Type: TEXT</P>
513          */
514         public static final String EXDATE = "exdate";
515 
516         /**
517          * The _sync_id of the original recurring event for which this event is
518          * an exception.
519          * <P>Type: TEXT</P>
520          */
521         public static final String ORIGINAL_EVENT = "originalEvent";
522 
523         /**
524          * The original instance time of the recurring event for which this
525          * event is an exception.
526          * <P>Type: INTEGER (long; millis since epoch)</P>
527          */
528         public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
529 
530         /**
531          * The allDay status (true or false) of the original recurring event
532          * for which this event is an exception.
533          * <P>Type: INTEGER (boolean)</P>
534          */
535         public static final String ORIGINAL_ALL_DAY = "originalAllDay";
536 
537         /**
538          * The last date this event repeats on, or NULL if it never ends
539          * <P>Type: INTEGER (long; millis since epoch)</P>
540          */
541         public static final String LAST_DATE = "lastDate";
542 
543         /**
544          * Whether the event has attendee information.  True if the event
545          * has full attendee data, false if the event has information about
546          * self only.
547          * <P>Type: INTEGER (boolean)</P>
548          */
549         public static final String HAS_ATTENDEE_DATA = "hasAttendeeData";
550 
551         /**
552          * Whether guests can modify the event.
553          * <P>Type: INTEGER (boolean)</P>
554          */
555         public static final String GUESTS_CAN_MODIFY = "guestsCanModify";
556 
557         /**
558          * Whether guests can invite other guests.
559          * <P>Type: INTEGER (boolean)</P>
560          */
561         public static final String GUESTS_CAN_INVITE_OTHERS = "guestsCanInviteOthers";
562 
563         /**
564          * Whether guests can see the list of attendees.
565          * <P>Type: INTEGER (boolean)</P>
566          */
567         public static final String GUESTS_CAN_SEE_GUESTS = "guestsCanSeeGuests";
568 
569         /**
570          * Email of the organizer (owner) of the event.
571          * <P>Type: STRING</P>
572          */
573         public static final String ORGANIZER = "organizer";
574 
575         /**
576          * Whether the user can invite others to the event.
577          * The GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary guest,
578          * while CAN_INVITE_OTHERS indicates if the user can invite others (either through
579          * GUESTS_CAN_INVITE_OTHERS or because the user has modify access to the event).
580          * <P>Type: INTEGER (boolean, readonly)</P>
581          */
582         public static final String CAN_INVITE_OTHERS = "canInviteOthers";
583 
584         /**
585          * The owner account for this calendar, based on the calendar (foreign
586          * key into the calendars table).
587          * <P>Type: String</P>
588          */
589         public static final String OWNER_ACCOUNT = "ownerAccount";
590 
591         /**
592          * Whether the row has been deleted.  A deleted row should be ignored.
593          * <P>Type: INTEGER (boolean)</P>
594          */
595         public static final String DELETED = "deleted";
596     }
597 
598     /**
599      * Contains one entry per calendar event. Recurring events show up as a single entry.
600      */
601     public static final class EventsEntity implements BaseColumns, EventsColumns, CalendarsColumns {
602         /**
603          * The content:// style URL for this table
604          */
605         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
606                 "/event_entities");
607 
newEntityIterator(Cursor cursor, ContentResolver resolver)608         public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
609             return new EntityIteratorImpl(cursor, resolver);
610         }
611 
newEntityIterator(Cursor cursor, ContentProviderClient provider)612         public static EntityIterator newEntityIterator(Cursor cursor,
613                 ContentProviderClient provider) {
614             return new EntityIteratorImpl(cursor, provider);
615         }
616 
617         private static class EntityIteratorImpl extends CursorEntityIterator {
618             private final ContentResolver mResolver;
619             private final ContentProviderClient mProvider;
620 
621             private static final String[] REMINDERS_PROJECTION = new String[] {
622                     Reminders.MINUTES,
623                     Reminders.METHOD,
624             };
625             private static final int COLUMN_MINUTES = 0;
626             private static final int COLUMN_METHOD = 1;
627 
628             private static final String[] ATTENDEES_PROJECTION = new String[] {
629                     Attendees.ATTENDEE_NAME,
630                     Attendees.ATTENDEE_EMAIL,
631                     Attendees.ATTENDEE_RELATIONSHIP,
632                     Attendees.ATTENDEE_TYPE,
633                     Attendees.ATTENDEE_STATUS,
634             };
635             private static final int COLUMN_ATTENDEE_NAME = 0;
636             private static final int COLUMN_ATTENDEE_EMAIL = 1;
637             private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
638             private static final int COLUMN_ATTENDEE_TYPE = 3;
639             private static final int COLUMN_ATTENDEE_STATUS = 4;
640             private static final String[] EXTENDED_PROJECTION = new String[] {
641                     ExtendedProperties._ID,
642                     ExtendedProperties.NAME,
643                     ExtendedProperties.VALUE
644             };
645             private static final int COLUMN_ID = 0;
646             private static final int COLUMN_NAME = 1;
647             private static final int COLUMN_VALUE = 2;
648 
649             private static final String WHERE_EVENT_ID = "event_id=?";
650 
EntityIteratorImpl(Cursor cursor, ContentResolver resolver)651             public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
652                 super(cursor);
653                 mResolver = resolver;
654                 mProvider = null;
655             }
656 
EntityIteratorImpl(Cursor cursor, ContentProviderClient provider)657             public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
658                 super(cursor);
659                 mResolver = null;
660                 mProvider = provider;
661             }
662 
663             @Override
getEntityAndIncrementCursor(Cursor cursor)664             public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
665                 // we expect the cursor is already at the row we need to read from
666                 final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
667                 ContentValues cv = new ContentValues();
668                 cv.put(Events._ID, eventId);
669                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
670                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HTML_URI);
671                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
672                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
673                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
674                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
675                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
676                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, COMMENTS_URI);
677                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
678                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
679                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
680                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
681                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
682                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBILITY);
683                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, TRANSPARENCY);
684                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
685                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
686                         HAS_EXTENDED_PROPERTIES);
687                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
688                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
689                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
690                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
691                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_EVENT);
692                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
693                         ORIGINAL_INSTANCE_TIME);
694                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
695                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
696                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
697                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
698                         GUESTS_CAN_INVITE_OTHERS);
699                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
700                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
701                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
702                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
703                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
704                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
705                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
706                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
707                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.URL);
708 
709                 Entity entity = new Entity(cv);
710                 Cursor subCursor;
711                 if (mResolver != null) {
712                     subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
713                             WHERE_EVENT_ID,
714                             new String[] { Long.toString(eventId) }  /* selectionArgs */,
715                             null /* sortOrder */);
716                 } else {
717                     subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
718                             WHERE_EVENT_ID,
719                             new String[] { Long.toString(eventId) }  /* selectionArgs */,
720                             null /* sortOrder */);
721                 }
722                 try {
723                     while (subCursor.moveToNext()) {
724                         ContentValues reminderValues = new ContentValues();
725                         reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
726                         reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
727                         entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
728                     }
729                 } finally {
730                     subCursor.close();
731                 }
732 
733                 if (mResolver != null) {
734                     subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
735                             WHERE_EVENT_ID,
736                             new String[] { Long.toString(eventId) } /* selectionArgs */,
737                             null /* sortOrder */);
738                 } else {
739                     subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
740                             WHERE_EVENT_ID,
741                             new String[] { Long.toString(eventId) } /* selectionArgs */,
742                             null /* sortOrder */);
743                 }
744                 try {
745                     while (subCursor.moveToNext()) {
746                         ContentValues attendeeValues = new ContentValues();
747                         attendeeValues.put(Attendees.ATTENDEE_NAME,
748                                 subCursor.getString(COLUMN_ATTENDEE_NAME));
749                         attendeeValues.put(Attendees.ATTENDEE_EMAIL,
750                                 subCursor.getString(COLUMN_ATTENDEE_EMAIL));
751                         attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
752                                 subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
753                         attendeeValues.put(Attendees.ATTENDEE_TYPE,
754                                 subCursor.getInt(COLUMN_ATTENDEE_TYPE));
755                         attendeeValues.put(Attendees.ATTENDEE_STATUS,
756                                 subCursor.getInt(COLUMN_ATTENDEE_STATUS));
757                         entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
758                     }
759                 } finally {
760                     subCursor.close();
761                 }
762 
763                 if (mResolver != null) {
764                     subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
765                             WHERE_EVENT_ID,
766                             new String[] { Long.toString(eventId) } /* selectionArgs */,
767                             null /* sortOrder */);
768                 } else {
769                     subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
770                             WHERE_EVENT_ID,
771                             new String[] { Long.toString(eventId) } /* selectionArgs */,
772                             null /* sortOrder */);
773                 }
774                 try {
775                     while (subCursor.moveToNext()) {
776                         ContentValues extendedValues = new ContentValues();
777                         extendedValues.put(ExtendedProperties._ID,
778                                 subCursor.getString(COLUMN_ID));
779                         extendedValues.put(ExtendedProperties.NAME,
780                                 subCursor.getString(COLUMN_NAME));
781                         extendedValues.put(ExtendedProperties.VALUE,
782                                 subCursor.getString(COLUMN_VALUE));
783                         entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
784                     }
785                 } finally {
786                     subCursor.close();
787                 }
788 
789                 cursor.moveToNext();
790                 return entity;
791             }
792         }
793     }
794 
795     /**
796      * Contains one entry per calendar event. Recurring events show up as a single entry.
797      */
798     public static final class Events implements BaseColumns, EventsColumns, CalendarsColumns {
799 
800         private static final String[] FETCH_ENTRY_COLUMNS =
801                 new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
802 
803         private static final String[] ATTENDEES_COLUMNS =
804                 new String[] { AttendeesColumns.ATTENDEE_NAME,
805                                AttendeesColumns.ATTENDEE_EMAIL,
806                                AttendeesColumns.ATTENDEE_RELATIONSHIP,
807                                AttendeesColumns.ATTENDEE_TYPE,
808                                AttendeesColumns.ATTENDEE_STATUS };
809 
query(ContentResolver cr, String[] projection)810         public static final Cursor query(ContentResolver cr, String[] projection) {
811             return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
812         }
813 
query(ContentResolver cr, String[] projection, String where, String orderBy)814         public static final Cursor query(ContentResolver cr, String[] projection,
815                                        String where, String orderBy) {
816             return cr.query(CONTENT_URI, projection, where,
817                                          null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
818         }
819 
extractValue(ICalendar.Component component, String propertyName)820         private static String extractValue(ICalendar.Component component,
821                                            String propertyName) {
822             ICalendar.Property property =
823                     component.getFirstProperty(propertyName);
824             if (property != null) {
825                 return property.getValue();
826             }
827             return null;
828         }
829 
830         /**
831          * The content:// style URL for this table
832          */
833         public static final Uri CONTENT_URI =
834                 Uri.parse("content://" + AUTHORITY + "/events");
835 
836         public static final Uri DELETED_CONTENT_URI =
837                 Uri.parse("content://" + AUTHORITY + "/deleted_events");
838 
839         /**
840          * The default sort order for this table
841          */
842         public static final String DEFAULT_SORT_ORDER = "";
843     }
844 
845     /**
846      * Contains one entry per calendar event instance. Recurring events show up every time
847      * they occur.
848      */
849     public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
850 
851         private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
852 
query(ContentResolver cr, String[] projection, long begin, long end)853         public static final Cursor query(ContentResolver cr, String[] projection,
854                                          long begin, long end) {
855             Uri.Builder builder = CONTENT_URI.buildUpon();
856             ContentUris.appendId(builder, begin);
857             ContentUris.appendId(builder, end);
858             return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
859                          null, DEFAULT_SORT_ORDER);
860         }
861 
query(ContentResolver cr, String[] projection, long begin, long end, String where, String orderBy)862         public static final Cursor query(ContentResolver cr, String[] projection,
863                                          long begin, long end, String where, String orderBy) {
864             Uri.Builder builder = CONTENT_URI.buildUpon();
865             ContentUris.appendId(builder, begin);
866             ContentUris.appendId(builder, end);
867             if (TextUtils.isEmpty(where)) {
868                 where = WHERE_CALENDARS_SELECTED;
869             } else {
870                 where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
871             }
872             return cr.query(builder.build(), projection, where,
873                          null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
874         }
875 
876         /**
877          * The content:// style URL for this table
878          */
879         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
880                 "/instances/when");
881         public static final Uri CONTENT_BY_DAY_URI =
882             Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
883 
884         /**
885          * The default sort order for this table.
886          */
887         public static final String DEFAULT_SORT_ORDER = "begin ASC";
888 
889         /**
890          * The sort order is: events with an earlier start time occur
891          * first and if the start times are the same, then events with
892          * a later end time occur first. The later end time is ordered
893          * first so that long-running events in the calendar views appear
894          * first.  If the start and end times of two events are
895          * the same then we sort alphabetically on the title.  This isn't
896          * required for correctness, it just adds a nice touch.
897          */
898         public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
899 
900         /**
901          * The beginning time of the instance, in UTC milliseconds
902          * <P>Type: INTEGER (long; millis since epoch)</P>
903          */
904         public static final String BEGIN = "begin";
905 
906         /**
907          * The ending time of the instance, in UTC milliseconds
908          * <P>Type: INTEGER (long; millis since epoch)</P>
909          */
910         public static final String END = "end";
911 
912         /**
913          * The event for this instance
914          * <P>Type: INTEGER (long, foreign key to the Events table)</P>
915          */
916         public static final String EVENT_ID = "event_id";
917 
918         /**
919          * The Julian start day of the instance, relative to the local timezone
920          * <P>Type: INTEGER (int)</P>
921          */
922         public static final String START_DAY = "startDay";
923 
924         /**
925          * The Julian end day of the instance, relative to the local timezone
926          * <P>Type: INTEGER (int)</P>
927          */
928         public static final String END_DAY = "endDay";
929 
930         /**
931          * The start minute of the instance measured from midnight in the
932          * local timezone.
933          * <P>Type: INTEGER (int)</P>
934          */
935         public static final String START_MINUTE = "startMinute";
936 
937         /**
938          * The end minute of the instance measured from midnight in the
939          * local timezone.
940          * <P>Type: INTEGER (int)</P>
941          */
942         public static final String END_MINUTE = "endMinute";
943     }
944 
945     /**
946      * CalendarCache stores some settings for calendar including the current
947      * time zone for the app. These settings are stored using a key/value
948      * scheme.
949      */
950     public interface CalendarCacheColumns {
951         /**
952          * The key for the setting. Keys are defined in CalendarChache in the
953          * Calendar provider.
954          * TODO Add keys to this file
955          */
956         public static final String KEY = "key";
957 
958         /**
959          * The value of the given setting.
960          */
961         public static final String VALUE = "value";
962     }
963 
964     public static class CalendarCache implements CalendarCacheColumns {
965         /**
966          * The URI to use for retrieving the properties from the Calendar db.
967          */
968         public static final Uri URI =
969                 Uri.parse("content://" + AUTHORITY + "/properties");
970         public static final String[] POJECTION = { KEY, VALUE };
971 
972         /**
973          * If updating a property, this must be provided as the selection. All
974          * other selections will fail. For queries this field can be omitted to
975          * retrieve all properties or used to query a single property. Valid
976          * keys include {@link #TIMEZONE_KEY_TYPE},
977          * {@link #TIMEZONE_KEY_INSTANCES}, and
978          * {@link #TIMEZONE_KEY_INSTANCES_PREVIOUS}, though the last one can
979          * only be read, not written.
980          */
981         public static final String WHERE = "key=?";
982 
983         /**
984          * They key for updating the use of auto/home time zones in Calendar.
985          * Valid values are {@link #TIMEZONE_TYPE_AUTO} or
986          * {@link #TIMEZONE_TYPE_HOME}.
987          */
988         public static final String TIMEZONE_KEY_TYPE = "timezoneType";
989 
990         /**
991          * The key for updating the time zone used by the provider when it
992          * generates the instances table. This should only be written if the
993          * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id
994          * should be written to this field.
995          */
996         public static final String TIMEZONE_KEY_INSTANCES = "timezoneInstances";
997 
998         /**
999          * The key for reading the last time zone set by the user. This should
1000          * only be read by apps and it will be automatically updated whenever
1001          * {@link #TIMEZONE_KEY_INSTANCES} is updated with
1002          * {@link #TIMEZONE_TYPE_HOME} set.
1003          */
1004         public static final String TIMEZONE_KEY_INSTANCES_PREVIOUS = "timezoneInstancesPrevious";
1005 
1006         /**
1007          * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
1008          * should stay in sync with the device's time zone.
1009          */
1010         public static final String TIMEZONE_TYPE_AUTO = "auto";
1011 
1012         /**
1013          * The value to write to {@link #TIMEZONE_KEY_TYPE} if the provider
1014          * should use a fixed time zone set by the user.
1015          */
1016         public static final String TIMEZONE_TYPE_HOME = "home";
1017     }
1018 
1019     /**
1020      * A few Calendar globals are needed in the CalendarProvider for expanding
1021      * the Instances table and these are all stored in the first (and only)
1022      * row of the CalendarMetaData table.
1023      */
1024     public interface CalendarMetaDataColumns {
1025         /**
1026          * The local timezone that was used for precomputing the fields
1027          * in the Instances table.
1028          */
1029         public static final String LOCAL_TIMEZONE = "localTimezone";
1030 
1031         /**
1032          * The minimum time used in expanding the Instances table,
1033          * in UTC milliseconds.
1034          * <P>Type: INTEGER</P>
1035          */
1036         public static final String MIN_INSTANCE = "minInstance";
1037 
1038         /**
1039          * The maximum time used in expanding the Instances table,
1040          * in UTC milliseconds.
1041          * <P>Type: INTEGER</P>
1042          */
1043         public static final String MAX_INSTANCE = "maxInstance";
1044 
1045         /**
1046          * The minimum Julian day in the EventDays table.
1047          * <P>Type: INTEGER</P>
1048          */
1049         public static final String MIN_EVENTDAYS = "minEventDays";
1050 
1051         /**
1052          * The maximum Julian day in the EventDays table.
1053          * <P>Type: INTEGER</P>
1054          */
1055         public static final String MAX_EVENTDAYS = "maxEventDays";
1056     }
1057 
1058     public static final class CalendarMetaData implements CalendarMetaDataColumns {
1059     }
1060 
1061     public interface EventDaysColumns {
1062         /**
1063          * The Julian starting day number.
1064          * <P>Type: INTEGER (int)</P>
1065          */
1066         public static final String STARTDAY = "startDay";
1067         public static final String ENDDAY = "endDay";
1068 
1069     }
1070 
1071     public static final class EventDays implements EventDaysColumns {
1072         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1073                 "/instances/groupbyday");
1074 
1075         public static final String[] PROJECTION = { STARTDAY, ENDDAY };
1076         public static final String SELECTION = "selected=1";
1077 
1078         /**
1079          * Retrieves the days with events for the Julian days starting at "startDay"
1080          * for "numDays".
1081          *
1082          * @param cr the ContentResolver
1083          * @param startDay the first Julian day in the range
1084          * @param numDays the number of days to load (must be at least 1)
1085          * @return a database cursor
1086          */
query(ContentResolver cr, int startDay, int numDays)1087         public static final Cursor query(ContentResolver cr, int startDay, int numDays) {
1088             if (numDays < 1) {
1089                 return null;
1090             }
1091             int endDay = startDay + numDays - 1;
1092             Uri.Builder builder = CONTENT_URI.buildUpon();
1093             ContentUris.appendId(builder, startDay);
1094             ContentUris.appendId(builder, endDay);
1095             return cr.query(builder.build(), PROJECTION, SELECTION,
1096                     null /* selection args */, STARTDAY);
1097         }
1098     }
1099 
1100     public interface RemindersColumns {
1101         /**
1102          * The event the reminder belongs to
1103          * <P>Type: INTEGER (foreign key to the Events table)</P>
1104          */
1105         public static final String EVENT_ID = "event_id";
1106 
1107         /**
1108          * The minutes prior to the event that the alarm should ring.  -1
1109          * specifies that we should use the default value for the system.
1110          * <P>Type: INTEGER</P>
1111          */
1112         public static final String MINUTES = "minutes";
1113 
1114         public static final int MINUTES_DEFAULT = -1;
1115 
1116         /**
1117          * The alarm method, as set on the server.  DEFAULT, ALERT, EMAIL, and
1118          * SMS are possible values; the device will only process DEFAULT and
1119          * ALERT reminders (the other types are simply stored so we can send the
1120          * same reminder info back to the server when we make changes).
1121          */
1122         public static final String METHOD = "method";
1123 
1124         public static final int METHOD_DEFAULT = 0;
1125         public static final int METHOD_ALERT = 1;
1126         public static final int METHOD_EMAIL = 2;
1127         public static final int METHOD_SMS = 3;
1128     }
1129 
1130     public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
1131         public static final String TABLE_NAME = "Reminders";
1132         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
1133     }
1134 
1135     public interface CalendarAlertsColumns {
1136         /**
1137          * The event that the alert belongs to
1138          * <P>Type: INTEGER (foreign key to the Events table)</P>
1139          */
1140         public static final String EVENT_ID = "event_id";
1141 
1142         /**
1143          * The start time of the event, in UTC
1144          * <P>Type: INTEGER (long; millis since epoch)</P>
1145          */
1146         public static final String BEGIN = "begin";
1147 
1148         /**
1149          * The end time of the event, in UTC
1150          * <P>Type: INTEGER (long; millis since epoch)</P>
1151          */
1152         public static final String END = "end";
1153 
1154         /**
1155          * The alarm time of the event, in UTC
1156          * <P>Type: INTEGER (long; millis since epoch)</P>
1157          */
1158         public static final String ALARM_TIME = "alarmTime";
1159 
1160         /**
1161          * The creation time of this database entry, in UTC.
1162          * (Useful for debugging missed reminders.)
1163          * <P>Type: INTEGER (long; millis since epoch)</P>
1164          */
1165         public static final String CREATION_TIME = "creationTime";
1166 
1167         /**
1168          * The time that the alarm broadcast was received by the Calendar app,
1169          * in UTC. (Useful for debugging missed reminders.)
1170          * <P>Type: INTEGER (long; millis since epoch)</P>
1171          */
1172         public static final String RECEIVED_TIME = "receivedTime";
1173 
1174         /**
1175          * The time that the notification was created by the Calendar app,
1176          * in UTC. (Useful for debugging missed reminders.)
1177          * <P>Type: INTEGER (long; millis since epoch)</P>
1178          */
1179         public static final String NOTIFY_TIME = "notifyTime";
1180 
1181         /**
1182          * The state of this alert.  It starts out as SCHEDULED, then when
1183          * the alarm goes off, it changes to FIRED, and then when the user
1184          * dismisses the alarm it changes to DISMISSED.
1185          * <P>Type: INTEGER</P>
1186          */
1187         public static final String STATE = "state";
1188 
1189         public static final int SCHEDULED = 0;
1190         public static final int FIRED = 1;
1191         public static final int DISMISSED = 2;
1192 
1193         /**
1194          * The number of minutes that this alarm precedes the start time
1195          * <P>Type: INTEGER </P>
1196          */
1197         public static final String MINUTES = "minutes";
1198 
1199         /**
1200          * The default sort order for this table
1201          */
1202         public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
1203     }
1204 
1205     public static final class CalendarAlerts implements BaseColumns,
1206             CalendarAlertsColumns, EventsColumns, CalendarsColumns {
1207 
1208         public static final String TABLE_NAME = "CalendarAlerts";
1209         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1210                 "/calendar_alerts");
1211 
1212         private static final String WHERE_ALARM_EXISTS = EVENT_ID + "=?"
1213                 + " AND " + BEGIN + "=?"
1214                 + " AND " + ALARM_TIME + "=?";
1215 
1216         private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?";
1217         private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC";
1218 
1219         private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + SCHEDULED
1220                 + " AND " + ALARM_TIME + "<?"
1221                 + " AND " + ALARM_TIME + ">?"
1222                 + " AND " + END + ">=?";
1223 
1224         /**
1225          * This URI is for grouping the query results by event_id and begin
1226          * time.  This will return one result per instance of an event.  So
1227          * events with multiple alarms will appear just once, but multiple
1228          * instances of a repeating event will show up multiple times.
1229          */
1230         public static final Uri CONTENT_URI_BY_INSTANCE =
1231             Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
1232 
1233         private static final boolean DEBUG = true;
1234 
insert(ContentResolver cr, long eventId, long begin, long end, long alarmTime, int minutes)1235         public static final Uri insert(ContentResolver cr, long eventId,
1236                 long begin, long end, long alarmTime, int minutes) {
1237             ContentValues values = new ContentValues();
1238             values.put(CalendarAlerts.EVENT_ID, eventId);
1239             values.put(CalendarAlerts.BEGIN, begin);
1240             values.put(CalendarAlerts.END, end);
1241             values.put(CalendarAlerts.ALARM_TIME, alarmTime);
1242             long currentTime = System.currentTimeMillis();
1243             values.put(CalendarAlerts.CREATION_TIME, currentTime);
1244             values.put(CalendarAlerts.RECEIVED_TIME, 0);
1245             values.put(CalendarAlerts.NOTIFY_TIME, 0);
1246             values.put(CalendarAlerts.STATE, SCHEDULED);
1247             values.put(CalendarAlerts.MINUTES, minutes);
1248             return cr.insert(CONTENT_URI, values);
1249         }
1250 
query(ContentResolver cr, String[] projection, String selection, String[] selectionArgs, String sortOrder)1251         public static final Cursor query(ContentResolver cr, String[] projection,
1252                 String selection, String[] selectionArgs, String sortOrder) {
1253             return cr.query(CONTENT_URI, projection, selection, selectionArgs,
1254                     sortOrder);
1255         }
1256 
1257         /**
1258          * Finds the next alarm after (or equal to) the given time and returns
1259          * the time of that alarm or -1 if no such alarm exists.
1260          *
1261          * @param cr the ContentResolver
1262          * @param millis the time in UTC milliseconds
1263          * @return the next alarm time greater than or equal to "millis", or -1
1264          *     if no such alarm exists.
1265          */
findNextAlarmTime(ContentResolver cr, long millis)1266         public static final long findNextAlarmTime(ContentResolver cr, long millis) {
1267             String selection = ALARM_TIME + ">=" + millis;
1268             // TODO: construct an explicit SQL query so that we can add
1269             // "LIMIT 1" to the end and get just one result.
1270             String[] projection = new String[] { ALARM_TIME };
1271             Cursor cursor = query(cr, projection,
1272                     WHERE_FINDNEXTALARMTIME,
1273                     new String[] {
1274                         Long.toString(millis)
1275                     },
1276                     SORT_ORDER_ALARMTIME_ASC);
1277             long alarmTime = -1;
1278             try {
1279                 if (cursor != null && cursor.moveToFirst()) {
1280                     alarmTime = cursor.getLong(0);
1281                 }
1282             } finally {
1283                 if (cursor != null) {
1284                     cursor.close();
1285                 }
1286             }
1287             return alarmTime;
1288         }
1289 
1290         /**
1291          * Searches the CalendarAlerts table for alarms that should have fired
1292          * but have not and then reschedules them.  This method can be called
1293          * at boot time to restore alarms that may have been lost due to a
1294          * phone reboot.
1295          *
1296          * @param cr the ContentResolver
1297          * @param context the Context
1298          * @param manager the AlarmManager
1299          */
rescheduleMissedAlarms(ContentResolver cr, Context context, AlarmManager manager)1300         public static final void rescheduleMissedAlarms(ContentResolver cr,
1301                 Context context, AlarmManager manager) {
1302             // Get all the alerts that have been scheduled but have not fired
1303             // and should have fired by now and are not too old.
1304             long now = System.currentTimeMillis();
1305             long ancient = now - DateUtils.DAY_IN_MILLIS;
1306             String[] projection = new String[] {
1307                     ALARM_TIME,
1308             };
1309 
1310             // TODO: construct an explicit SQL query so that we can add
1311             // "GROUPBY" instead of doing a sort and de-dup
1312             Cursor cursor = CalendarAlerts.query(cr,
1313                     projection,
1314                     WHERE_RESCHEDULE_MISSED_ALARMS,
1315                     new String[] {
1316                         Long.toString(now),
1317                         Long.toString(ancient),
1318                         Long.toString(now)
1319                     },
1320                     SORT_ORDER_ALARMTIME_ASC);
1321             if (cursor == null) {
1322                 return;
1323             }
1324 
1325             if (DEBUG) {
1326                 Log.d(TAG, "missed alarms found: " + cursor.getCount());
1327             }
1328 
1329             try {
1330                 long alarmTime = -1;
1331 
1332                 while (cursor.moveToNext()) {
1333                     long newAlarmTime = cursor.getLong(0);
1334                     if (alarmTime != newAlarmTime) {
1335                         if (DEBUG) {
1336                             Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
1337                         }
1338                         scheduleAlarm(context, manager, newAlarmTime);
1339                         alarmTime = newAlarmTime;
1340                     }
1341                 }
1342             } finally {
1343                 cursor.close();
1344             }
1345         }
1346 
scheduleAlarm(Context context, AlarmManager manager, long alarmTime)1347         public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
1348             if (DEBUG) {
1349                 Time time = new Time();
1350                 time.set(alarmTime);
1351                 String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
1352                 Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
1353             }
1354 
1355             if (manager == null) {
1356                 manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
1357             }
1358 
1359             Intent intent = new Intent(EVENT_REMINDER_ACTION);
1360             intent.setData(ContentUris.withAppendedId(Calendar.CONTENT_URI, alarmTime));
1361             intent.putExtra(ALARM_TIME, alarmTime);
1362             PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
1363             manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
1364         }
1365 
1366         /**
1367          * Searches for an entry in the CalendarAlerts table that matches
1368          * the given event id, begin time and alarm time.  If one is found
1369          * then this alarm already exists and this method returns true.
1370          *
1371          * @param cr the ContentResolver
1372          * @param eventId the event id to match
1373          * @param begin the start time of the event in UTC millis
1374          * @param alarmTime the alarm time of the event in UTC millis
1375          * @return true if there is already an alarm for the given event
1376          *   with the same start time and alarm time.
1377          */
alarmExists(ContentResolver cr, long eventId, long begin, long alarmTime)1378         public static final boolean alarmExists(ContentResolver cr, long eventId,
1379                 long begin, long alarmTime) {
1380             // TODO: construct an explicit SQL query so that we can add
1381             // "LIMIT 1" to the end and get just one result.
1382             String[] projection = new String[] { ALARM_TIME };
1383             Cursor cursor = query(cr,
1384                     projection,
1385                     WHERE_ALARM_EXISTS,
1386                     new String[] {
1387                         Long.toString(eventId),
1388                         Long.toString(begin),
1389                         Long.toString(alarmTime)
1390                     },
1391                     null);
1392             boolean found = false;
1393             try {
1394                 if (cursor != null && cursor.getCount() > 0) {
1395                     found = true;
1396                 }
1397             } finally {
1398                 if (cursor != null) {
1399                     cursor.close();
1400                 }
1401             }
1402             return found;
1403         }
1404     }
1405 
1406     public interface ExtendedPropertiesColumns {
1407         /**
1408          * The event the extended property belongs to
1409          * <P>Type: INTEGER (foreign key to the Events table)</P>
1410          */
1411         public static final String EVENT_ID = "event_id";
1412 
1413         /**
1414          * The name of the extended property.  This is a uri of the form
1415          * {scheme}#{local-name} convention.
1416          * <P>Type: TEXT</P>
1417          */
1418         public static final String NAME = "name";
1419 
1420         /**
1421          * The value of the extended property.
1422          * <P>Type: TEXT</P>
1423          */
1424         public static final String VALUE = "value";
1425     }
1426 
1427    public static final class ExtendedProperties implements BaseColumns,
1428             ExtendedPropertiesColumns, EventsColumns {
1429         public static final Uri CONTENT_URI =
1430                 Uri.parse("content://" + AUTHORITY + "/extendedproperties");
1431 
1432         // TODO: fill out this class when we actually start utilizing extendedproperties
1433         // in the calendar application.
1434    }
1435 
1436     /**
1437      * A table provided for sync adapters to use for storing private sync state data.
1438      *
1439      * @see SyncStateContract
1440      */
1441     public static final class SyncState implements SyncStateContract.Columns {
1442         /**
1443          * This utility class cannot be instantiated
1444          */
SyncState()1445         private SyncState() {}
1446 
1447         public static final String CONTENT_DIRECTORY =
1448                 SyncStateContract.Constants.CONTENT_DIRECTORY;
1449 
1450         /**
1451          * The content:// style URI for this table
1452          */
1453         public static final Uri CONTENT_URI =
1454                 Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
1455     }
1456 }
1457