blob: 3a221e436c01683661f24f397e178627e7fc8400 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.database.Cursor;
27import android.net.Uri;
28import android.pim.ICalendar;
29import android.pim.RecurrenceSet;
30import android.text.TextUtils;
31import android.text.format.DateUtils;
32import android.text.format.Time;
33import android.util.Config;
34import android.util.Log;
Fred Quintanad9d2f112009-04-23 13:36:27 -070035import android.accounts.Account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import com.android.internal.database.ArrayListCursor;
37import com.google.android.gdata.client.AndroidGDataClient;
38import com.google.android.gdata.client.AndroidXmlParserFactory;
39import com.google.wireless.gdata.calendar.client.CalendarClient;
40import com.google.wireless.gdata.calendar.data.EventEntry;
41import com.google.wireless.gdata.calendar.data.Who;
42import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
43import com.google.wireless.gdata.data.StringUtils;
44
45import java.util.ArrayList;
46import java.util.Vector;
47
48/**
49 * The Calendar provider contains all calendar events.
50 *
51 * @hide
52 */
53public final class Calendar {
54
55 public static final String TAG = "Calendar";
56
57 /**
58 * Broadcast Action: An event reminder.
59 */
60 public static final String
61 EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
62
63 /**
64 * These are the symbolic names for the keys used in the extra data
65 * passed in the intent for event reminders.
66 */
67 public static final String EVENT_BEGIN_TIME = "beginTime";
68 public static final String EVENT_END_TIME = "endTime";
69
70 public static final String AUTHORITY = "calendar";
71
72 /**
73 * The content:// style URL for the top-level calendar authority
74 */
75 public static final Uri CONTENT_URI =
76 Uri.parse("content://" + AUTHORITY);
77
78 /**
79 * Columns from the Calendars table that other tables join into themselves.
80 */
81 public interface CalendarsColumns
82 {
83 /**
84 * The color of the calendar
85 * <P>Type: INTEGER (color value)</P>
86 */
87 public static final String COLOR = "color";
88
89 /**
90 * The level of access that the user has for the calendar
91 * <P>Type: INTEGER (one of the values below)</P>
92 */
93 public static final String ACCESS_LEVEL = "access_level";
94
95 /** Cannot access the calendar */
96 public static final int NO_ACCESS = 0;
97 /** Can only see free/busy information about the calendar */
98 public static final int FREEBUSY_ACCESS = 100;
99 /** Can read all event details */
100 public static final int READ_ACCESS = 200;
101 public static final int RESPOND_ACCESS = 300;
102 public static final int OVERRIDE_ACCESS = 400;
103 /** Full access to modify the calendar, but not the access control settings */
104 public static final int CONTRIBUTOR_ACCESS = 500;
105 public static final int EDITOR_ACCESS = 600;
106 /** Full access to the calendar */
107 public static final int OWNER_ACCESS = 700;
108 public static final int ROOT_ACCESS = 800;
109
110 /**
111 * Is the calendar selected to be displayed?
112 * <P>Type: INTEGER (boolean)</P>
113 */
114 public static final String SELECTED = "selected";
115
116 /**
117 * The timezone the calendar's events occurs in
118 * <P>Type: TEXT</P>
119 */
120 public static final String TIMEZONE = "timezone";
121
122 /**
123 * If this calendar is in the list of calendars that are selected for
124 * syncing then "sync_events" is 1, otherwise 0.
125 * <p>Type: INTEGER (boolean)</p>
126 */
127 public static final String SYNC_EVENTS = "sync_events";
128 }
129
130 /**
131 * Contains a list of available calendars.
132 */
133 public static class Calendars implements BaseColumns, SyncConstValue, CalendarsColumns
134 {
135 public static final Cursor query(ContentResolver cr, String[] projection,
136 String where, String orderBy)
137 {
138 return cr.query(CONTENT_URI, projection, where,
139 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
140 }
141
142 /**
143 * Convenience method perform a delete on the Calendar provider
144 *
145 * @param cr the ContentResolver
146 * @param selection the rows to delete
147 * @return the count of rows that were deleted
148 */
149 public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
150 {
151 return cr.delete(CONTENT_URI, selection, selectionArgs);
152 }
153
154 /**
155 * Convenience method to delete all calendars that match the account.
156 *
157 * @param cr the ContentResolver
158 * @param account the account whose rows should be deleted
159 * @return the count of rows that were deleted
160 */
Fred Quintanad9d2f112009-04-23 13:36:27 -0700161 public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 // delete all calendars that match this account
Fred Quintanad9d2f112009-04-23 13:36:27 -0700163 return Calendar.Calendars.delete(cr,
164 Calendar.Calendars._SYNC_ACCOUNT + "=? AND "
165 + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=?",
166 new String[] {account.mName, account.mType});
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168
169 /**
170 * The content:// style URL for this table
171 */
172 public static final Uri CONTENT_URI =
173 Uri.parse("content://calendar/calendars");
174
175 public static final Uri LIVE_CONTENT_URI =
176 Uri.parse("content://calendar/calendars?update=1");
177
178 /**
179 * The default sort order for this table
180 */
181 public static final String DEFAULT_SORT_ORDER = "displayName";
182
183 /**
184 * The URL to the calendar
185 * <P>Type: TEXT (URL)</P>
186 */
187 public static final String URL = "url";
188
189 /**
190 * The name of the calendar
191 * <P>Type: TEXT</P>
192 */
193 public static final String NAME = "name";
194
195 /**
196 * The display name of the calendar
197 * <P>Type: TEXT</P>
198 */
199 public static final String DISPLAY_NAME = "displayName";
200
201 /**
202 * The location the of the events in the calendar
203 * <P>Type: TEXT</P>
204 */
205 public static final String LOCATION = "location";
206
207 /**
208 * Should the calendar be hidden in the calendar selection panel?
209 * <P>Type: INTEGER (boolean)</P>
210 */
211 public static final String HIDDEN = "hidden";
212 }
213
214 public interface AttendeesColumns {
215
216 /**
217 * The id of the event.
218 * <P>Type: INTEGER</P>
219 */
220 public static final String EVENT_ID = "event_id";
221
222 /**
223 * The name of the attendee.
224 * <P>Type: STRING</P>
225 */
226 public static final String ATTENDEE_NAME = "attendeeName";
227
228 /**
229 * The email address of the attendee.
230 * <P>Type: STRING</P>
231 */
232 public static final String ATTENDEE_EMAIL = "attendeeEmail";
233
234 /**
235 * The relationship of the attendee to the user.
236 * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.
237 */
238 public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
239
240 public static final int RELATIONSHIP_NONE = 0;
241 public static final int RELATIONSHIP_ATTENDEE = 1;
242 public static final int RELATIONSHIP_ORGANIZER = 2;
243 public static final int RELATIONSHIP_PERFORMER = 3;
244 public static final int RELATIONSHIP_SPEAKER = 4;
245
246 /**
247 * The type of attendee.
248 * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})
249 */
250 public static final String ATTENDEE_TYPE = "attendeeType";
251
252 public static final int TYPE_NONE = 0;
253 public static final int TYPE_REQUIRED = 1;
254 public static final int TYPE_OPTIONAL = 2;
255
256 /**
257 * The attendance status of the attendee.
258 * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...}.
259 */
260 public static final String ATTENDEE_STATUS = "attendeeStatus";
261
262 public static final int ATTENDEE_STATUS_NONE = 0;
263 public static final int ATTENDEE_STATUS_ACCEPTED = 1;
264 public static final int ATTENDEE_STATUS_DECLINED = 2;
265 public static final int ATTENDEE_STATUS_INVITED = 3;
266 public static final int ATTENDEE_STATUS_TENTATIVE = 4;
267 }
268
269 public static final class Attendees implements BaseColumns,
270 AttendeesColumns, EventsColumns {
271 public static final Uri CONTENT_URI =
272 Uri.parse("content://calendar/attendees");
273
274 // TODO: fill out this class when we actually start utilizing attendees
275 // in the calendar application.
276 }
277
278 /**
279 * Columns from the Events table that other tables join into themselves.
280 */
281 public interface EventsColumns
282 {
283 /**
284 * The calendar the event belongs to
285 * <P>Type: INTEGER (foreign key to the Calendars table)</P>
286 */
287 public static final String CALENDAR_ID = "calendar_id";
288
289 /**
290 * The URI for an HTML version of this event.
291 * <P>Type: TEXT</P>
292 */
293 public static final String HTML_URI = "htmlUri";
294
295 /**
296 * The title of the event
297 * <P>Type: TEXT</P>
298 */
299 public static final String TITLE = "title";
300
301 /**
302 * The description of the event
303 * <P>Type: TEXT</P>
304 */
305 public static final String DESCRIPTION = "description";
306
307 /**
308 * Where the event takes place.
309 * <P>Type: TEXT</P>
310 */
311 public static final String EVENT_LOCATION = "eventLocation";
312
313 /**
314 * The event status
315 * <P>Type: INTEGER (int)</P>
316 */
317 public static final String STATUS = "eventStatus";
318
319 public static final int STATUS_TENTATIVE = 0;
320 public static final int STATUS_CONFIRMED = 1;
321 public static final int STATUS_CANCELED = 2;
322
323 /**
324 * This is a copy of the attendee status for the owner of this event.
325 * This field is copied here so that we can efficiently filter out
326 * events that are declined without having to look in the Attendees
327 * table.
328 *
329 * <P>Type: INTEGER (int)</P>
330 */
331 public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
332
333 /**
334 * The comments feed uri.
335 * <P>Type: TEXT</P>
336 */
337 public static final String COMMENTS_URI = "commentsUri";
338
339 /**
340 * The time the event starts
341 * <P>Type: INTEGER (long; millis since epoch)</P>
342 */
343 public static final String DTSTART = "dtstart";
344
345 /**
346 * The time the event ends
347 * <P>Type: INTEGER (long; millis since epoch)</P>
348 */
349 public static final String DTEND = "dtend";
350
351 /**
352 * The duration of the event
353 * <P>Type: TEXT (duration in RFC2445 format)</P>
354 */
355 public static final String DURATION = "duration";
356
357 /**
358 * The timezone for the event.
359 * <P>Type: TEXT
360 */
361 public static final String EVENT_TIMEZONE = "eventTimezone";
362
363 /**
364 * Whether the event lasts all day or not
365 * <P>Type: INTEGER (boolean)</P>
366 */
367 public static final String ALL_DAY = "allDay";
368
369 /**
370 * Visibility for the event.
371 * <P>Type: INTEGER</P>
372 */
373 public static final String VISIBILITY = "visibility";
374
375 public static final int VISIBILITY_DEFAULT = 0;
376 public static final int VISIBILITY_CONFIDENTIAL = 1;
377 public static final int VISIBILITY_PRIVATE = 2;
378 public static final int VISIBILITY_PUBLIC = 3;
379
380 /**
381 * Transparency for the event -- does the event consume time on the calendar?
382 * <P>Type: INTEGER</P>
383 */
384 public static final String TRANSPARENCY = "transparency";
385
386 public static final int TRANSPARENCY_OPAQUE = 0;
387
388 public static final int TRANSPARENCY_TRANSPARENT = 1;
389
390 /**
391 * Whether the event has an alarm or not
392 * <P>Type: INTEGER (boolean)</P>
393 */
394 public static final String HAS_ALARM = "hasAlarm";
395
396 /**
397 * Whether the event has extended properties or not
398 * <P>Type: INTEGER (boolean)</P>
399 */
400 public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
401
402 /**
403 * The recurrence rule for the event.
404 * than one.
405 * <P>Type: TEXT</P>
406 */
407 public static final String RRULE = "rrule";
408
409 /**
410 * The recurrence dates for the event.
411 * <P>Type: TEXT</P>
412 */
413 public static final String RDATE = "rdate";
414
415 /**
416 * The recurrence exception rule for the event.
417 * <P>Type: TEXT</P>
418 */
419 public static final String EXRULE = "exrule";
420
421 /**
422 * The recurrence exception dates for the event.
423 * <P>Type: TEXT</P>
424 */
425 public static final String EXDATE = "exdate";
426
427 /**
428 * The _sync_id of the original recurring event for which this event is
429 * an exception.
430 * <P>Type: TEXT</P>
431 */
432 public static final String ORIGINAL_EVENT = "originalEvent";
433
434 /**
435 * The original instance time of the recurring event for which this
436 * event is an exception.
437 * <P>Type: INTEGER (long; millis since epoch)</P>
438 */
439 public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
440
441 /**
442 * The allDay status (true or false) of the original recurring event
443 * for which this event is an exception.
444 * <P>Type: INTEGER (boolean)</P>
445 */
446 public static final String ORIGINAL_ALL_DAY = "originalAllDay";
447
448 /**
449 * The last date this event repeats on, or NULL if it never ends
450 * <P>Type: INTEGER (long; millis since epoch)</P>
451 */
452 public static final String LAST_DATE = "lastDate";
453 }
454
455 /**
456 * Contains one entry per calendar event. Recurring events show up as a single entry.
457 */
458 public static final class Events implements BaseColumns, SyncConstValue,
459 EventsColumns, CalendarsColumns {
460
461 private static final String[] FETCH_ENTRY_COLUMNS =
462 new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
463
464 private static final String[] ATTENDEES_COLUMNS =
465 new String[] { AttendeesColumns.ATTENDEE_NAME,
466 AttendeesColumns.ATTENDEE_EMAIL,
467 AttendeesColumns.ATTENDEE_RELATIONSHIP,
468 AttendeesColumns.ATTENDEE_TYPE,
469 AttendeesColumns.ATTENDEE_STATUS };
470
471 private static CalendarClient sCalendarClient = null;
472
473 public static final Cursor query(ContentResolver cr, String[] projection) {
474 return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
475 }
476
477 public static final Cursor query(ContentResolver cr, String[] projection,
478 String where, String orderBy) {
479 return cr.query(CONTENT_URI, projection, where,
480 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
481 }
482
483 private static String extractValue(ICalendar.Component component,
484 String propertyName) {
485 ICalendar.Property property =
486 component.getFirstProperty(propertyName);
487 if (property != null) {
488 return property.getValue();
489 }
490 return null;
491 }
492
493 public static final Uri insertVEvent(ContentResolver cr,
494 ICalendar.Component event, long calendarId, int status,
495 ContentValues values) {
496
497 // TODO: define VEVENT component names as constants in some
498 // appropriate class (ICalendar.Component?).
499
500 values.clear();
501
502 // title
503 String title = extractValue(event, "SUMMARY");
504 if (TextUtils.isEmpty(title)) {
505 if (Config.LOGD) {
506 Log.d(TAG, "No SUMMARY provided for event. "
507 + "Cannot import.");
508 }
509 return null;
510 }
511 values.put(TITLE, title);
512
513 // status
514 values.put(STATUS, status);
515
516 // description
517 String description = extractValue(event, "DESCRIPTION");
518 if (!TextUtils.isEmpty(description)) {
519 values.put(DESCRIPTION, description);
520 }
521
522 // where
523 String where = extractValue(event, "LOCATION");
524 if (!StringUtils.isEmpty(where)) {
525 values.put(EVENT_LOCATION, where);
526 }
527
528 // Calendar ID
529 values.put(CALENDAR_ID, calendarId);
530
531 boolean timesSet = false;
532
533 // TODO: deal with VALARMs
534
535 // dtstart & dtend
536 Time time = new Time(Time.TIMEZONE_UTC);
537 String dtstart = null;
538 String dtend = null;
539 String duration = null;
540 ICalendar.Property dtstartProp = event.getFirstProperty("DTSTART");
541 // TODO: handle "floating" timezone (no timezone specified).
542 if (dtstartProp != null) {
543 dtstart = dtstartProp.getValue();
544 if (!TextUtils.isEmpty(dtstart)) {
545 ICalendar.Parameter tzidParam =
546 dtstartProp.getFirstParameter("TZID");
547 if (tzidParam != null && tzidParam.value != null) {
548 time.clear(tzidParam.value);
549 }
550 try {
551 time.parse(dtstart);
552 } catch (Exception e) {
553 if (Config.LOGD) {
554 Log.d(TAG, "Cannot parse dtstart " + dtstart, e);
555 }
556 return null;
557 }
558 if (time.allDay) {
559 values.put(ALL_DAY, 1);
560 }
561 values.put(DTSTART, time.toMillis(false /* use isDst */));
562 values.put(EVENT_TIMEZONE, time.timezone);
563 }
564
565 ICalendar.Property dtendProp = event.getFirstProperty("DTEND");
566 if (dtendProp != null) {
567 dtend = dtendProp.getValue();
568 if (!TextUtils.isEmpty(dtend)) {
569 // TODO: make sure the timezones are the same for
570 // start, end.
571 try {
572 time.parse(dtend);
573 } catch (Exception e) {
574 if (Config.LOGD) {
575 Log.d(TAG, "Cannot parse dtend " + dtend, e);
576 }
577 return null;
578 }
579 values.put(DTEND, time.toMillis(false /* use isDst */));
580 }
581 } else {
582 // look for a duration
583 ICalendar.Property durationProp =
584 event.getFirstProperty("DURATION");
585 if (durationProp != null) {
586 duration = durationProp.getValue();
587 if (!TextUtils.isEmpty(duration)) {
588 // TODO: check that it is valid?
589 values.put(DURATION, duration);
590 }
591 }
592 }
593 }
594 if (TextUtils.isEmpty(dtstart) ||
595 (TextUtils.isEmpty(dtend) && TextUtils.isEmpty(duration))) {
596 if (Config.LOGD) {
597 Log.d(TAG, "No DTSTART or DTEND/DURATION defined.");
598 }
599 return null;
600 }
601
602 // rrule
603 if (!RecurrenceSet.populateContentValues(event, values)) {
604 return null;
605 }
606
607 return cr.insert(CONTENT_URI, values);
608 }
609
610 /**
611 * Returns a singleton instance of the CalendarClient used to fetch entries from the
612 * calendar server.
613 * @param cr The ContentResolver used to lookup the address of the calendar server in the
614 * settings database.
615 * @return The singleton instance of the CalendarClient used to fetch entries from the
616 * calendar server.
617 */
618 private static synchronized CalendarClient getCalendarClient(ContentResolver cr) {
619 if (sCalendarClient == null) {
620 sCalendarClient = new CalendarClient(
621 new AndroidGDataClient(cr),
622 new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
623 }
624 return sCalendarClient;
625 }
626
627 /**
628 * Extracts the attendees information out of event and adds it to a new ArrayList of columns
629 * within the supplied ArrayList of rows. These rows are expected to be used within an
630 * {@link ArrayListCursor}.
631 */
632 private static final void extractAttendeesIntoArrayList(EventEntry event,
633 ArrayList<ArrayList> rows) {
634 Log.d(TAG, "EVENT: " + event.toString());
635 Vector<Who> attendees = (Vector<Who>) event.getAttendees();
636
637 int numAttendees = attendees == null ? 0 : attendees.size();
638
639 for (int i = 0; i < numAttendees; ++i) {
640 Who attendee = attendees.elementAt(i);
641 ArrayList row = new ArrayList();
642 row.add(attendee.getValue());
643 row.add(attendee.getEmail());
644 row.add(attendee.getRelationship());
645 row.add(attendee.getType());
646 row.add(attendee.getStatus());
647 rows.add(row);
648 }
649 }
650
651 /**
652 * The content:// style URL for this table
653 */
654 public static final Uri CONTENT_URI =
655 Uri.parse("content://calendar/events");
656
657 public static final Uri DELETED_CONTENT_URI =
658 Uri.parse("content://calendar/deleted_events");
659
660 /**
661 * The default sort order for this table
662 */
663 public static final String DEFAULT_SORT_ORDER = "";
664 }
665
666 /**
667 * Contains one entry per calendar event instance. Recurring events show up every time
668 * they occur.
669 */
670 public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
671
672 public static final Cursor query(ContentResolver cr, String[] projection,
673 long begin, long end) {
674 Uri.Builder builder = CONTENT_URI.buildUpon();
675 ContentUris.appendId(builder, begin);
676 ContentUris.appendId(builder, end);
677 return cr.query(builder.build(), projection, Calendars.SELECTED + "=1",
678 null, DEFAULT_SORT_ORDER);
679 }
680
681 public static final Cursor query(ContentResolver cr, String[] projection,
682 long begin, long end, String where, String orderBy) {
683 Uri.Builder builder = CONTENT_URI.buildUpon();
684 ContentUris.appendId(builder, begin);
685 ContentUris.appendId(builder, end);
686 if (TextUtils.isEmpty(where)) {
687 where = Calendars.SELECTED + "=1";
688 } else {
689 where = "(" + where + ") AND " + Calendars.SELECTED + "=1";
690 }
691 return cr.query(builder.build(), projection, where,
692 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
693 }
694
695 /**
696 * The content:// style URL for this table
697 */
698 public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/when");
699
700 /**
701 * The default sort order for this table.
702 */
703 public static final String DEFAULT_SORT_ORDER = "begin ASC";
704
705 /**
706 * The sort order is: events with an earlier start time occur
707 * first and if the start times are the same, then events with
708 * a later end time occur first. The later end time is ordered
709 * first so that long-running events in the calendar views appear
710 * first. If the start and end times of two events are
711 * the same then we sort alphabetically on the title. This isn't
712 * required for correctness, it just adds a nice touch.
713 */
714 public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
715
716 /**
717 * The beginning time of the instance, in UTC milliseconds
718 * <P>Type: INTEGER (long; millis since epoch)</P>
719 */
720 public static final String BEGIN = "begin";
721
722 /**
723 * The ending time of the instance, in UTC milliseconds
724 * <P>Type: INTEGER (long; millis since epoch)</P>
725 */
726 public static final String END = "end";
727
728 /**
729 * The event for this instance
730 * <P>Type: INTEGER (long, foreign key to the Events table)</P>
731 */
732 public static final String EVENT_ID = "event_id";
733
734 /**
735 * The Julian start day of the instance, relative to the local timezone
736 * <P>Type: INTEGER (int)</P>
737 */
738 public static final String START_DAY = "startDay";
739
740 /**
741 * The Julian end day of the instance, relative to the local timezone
742 * <P>Type: INTEGER (int)</P>
743 */
744 public static final String END_DAY = "endDay";
745
746 /**
747 * The start minute of the instance measured from midnight in the
748 * local timezone.
749 * <P>Type: INTEGER (int)</P>
750 */
751 public static final String START_MINUTE = "startMinute";
752
753 /**
754 * The end minute of the instance measured from midnight in the
755 * local timezone.
756 * <P>Type: INTEGER (int)</P>
757 */
758 public static final String END_MINUTE = "endMinute";
759 }
760
761 /**
762 * A few Calendar globals are needed in the CalendarProvider for expanding
763 * the Instances table and these are all stored in the first (and only)
764 * row of the CalendarMetaData table.
765 */
766 public interface CalendarMetaDataColumns {
767 /**
768 * The local timezone that was used for precomputing the fields
769 * in the Instances table.
770 */
771 public static final String LOCAL_TIMEZONE = "localTimezone";
772
773 /**
774 * The minimum time used in expanding the Instances table,
775 * in UTC milliseconds.
776 * <P>Type: INTEGER</P>
777 */
778 public static final String MIN_INSTANCE = "minInstance";
779
780 /**
781 * The maximum time used in expanding the Instances table,
782 * in UTC milliseconds.
783 * <P>Type: INTEGER</P>
784 */
785 public static final String MAX_INSTANCE = "maxInstance";
786
787 /**
788 * The minimum Julian day in the BusyBits table.
789 * <P>Type: INTEGER</P>
790 */
791 public static final String MIN_BUSYBITS = "minBusyBits";
792
793 /**
794 * The maximum Julian day in the BusyBits table.
795 * <P>Type: INTEGER</P>
796 */
797 public static final String MAX_BUSYBITS = "maxBusyBits";
798 }
799
800 public static final class CalendarMetaData implements CalendarMetaDataColumns {
801 }
802
803 public interface BusyBitsColumns {
804 /**
805 * The Julian day number.
806 * <P>Type: INTEGER (int)</P>
807 */
808 public static final String DAY = "day";
809
810 /**
811 * The 24 bits representing the 24 1-hour time slots in a day.
812 * If an event in the Instances table overlaps part of a 1-hour
813 * time slot then the corresponding bit is set. The first time slot
814 * (12am to 1am) is bit 0. The last time slot (11pm to midnight)
815 * is bit 23.
816 * <P>Type: INTEGER (int)</P>
817 */
818 public static final String BUSYBITS = "busyBits";
819
820 /**
821 * The number of all-day events that occur on this day.
822 * <P>Type: INTEGER (int)</P>
823 */
824 public static final String ALL_DAY_COUNT = "allDayCount";
825 }
826
827 public static final class BusyBits implements BusyBitsColumns {
828 public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
829
830 public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
831
832 // The number of minutes represented by one busy bit
833 public static final int MINUTES_PER_BUSY_INTERVAL = 60;
834
835 // The number of intervals in a day
836 public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
837
838 /**
839 * Retrieves the busy bits for the Julian days starting at "startDay"
840 * for "numDays".
841 *
842 * @param cr the ContentResolver
843 * @param startDay the first Julian day in the range
844 * @param numDays the number of days to load (must be at least 1)
845 * @return a database cursor
846 */
847 public static final Cursor query(ContentResolver cr, int startDay, int numDays) {
848 if (numDays < 1) {
849 return null;
850 }
851 int endDay = startDay + numDays - 1;
852 Uri.Builder builder = CONTENT_URI.buildUpon();
853 ContentUris.appendId(builder, startDay);
854 ContentUris.appendId(builder, endDay);
855 return cr.query(builder.build(), PROJECTION, null /* selection */,
856 null /* selection args */, DAY);
857 }
858 }
859
860 public interface RemindersColumns {
861 /**
862 * The event the reminder belongs to
863 * <P>Type: INTEGER (foreign key to the Events table)</P>
864 */
865 public static final String EVENT_ID = "event_id";
866
867 /**
868 * The minutes prior to the event that the alarm should ring. -1
869 * specifies that we should use the default value for the system.
870 * <P>Type: INTEGER</P>
871 */
872 public static final String MINUTES = "minutes";
873
874 public static final int MINUTES_DEFAULT = -1;
875
876 /**
877 * The alarm method, as set on the server. DEFAULT, ALERT, EMAIL, and
878 * SMS are possible values; the device will only process DEFAULT and
879 * ALERT reminders (the other types are simply stored so we can send the
880 * same reminder info back to the server when we make changes).
881 */
882 public static final String METHOD = "method";
883
884 public static final int METHOD_DEFAULT = 0;
885 public static final int METHOD_ALERT = 1;
886 public static final int METHOD_EMAIL = 2;
887 public static final int METHOD_SMS = 3;
888 }
889
890 public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
891 public static final String TABLE_NAME = "Reminders";
892 public static final Uri CONTENT_URI = Uri.parse("content://calendar/reminders");
893 }
894
895 public interface CalendarAlertsColumns {
896 /**
897 * The event that the alert belongs to
898 * <P>Type: INTEGER (foreign key to the Events table)</P>
899 */
900 public static final String EVENT_ID = "event_id";
901
902 /**
903 * The start time of the event, in UTC
904 * <P>Type: INTEGER (long; millis since epoch)</P>
905 */
906 public static final String BEGIN = "begin";
907
908 /**
909 * The end time of the event, in UTC
910 * <P>Type: INTEGER (long; millis since epoch)</P>
911 */
912 public static final String END = "end";
913
914 /**
915 * The alarm time of the event, in UTC
916 * <P>Type: INTEGER (long; millis since epoch)</P>
917 */
918 public static final String ALARM_TIME = "alarmTime";
919
920 /**
921 * The creation time of this database entry, in UTC.
922 * (Useful for debugging missed reminders.)
923 * <P>Type: INTEGER (long; millis since epoch)</P>
924 */
925 public static final String CREATION_TIME = "creationTime";
926
927 /**
928 * The time that the alarm broadcast was received by the Calendar app,
929 * in UTC. (Useful for debugging missed reminders.)
930 * <P>Type: INTEGER (long; millis since epoch)</P>
931 */
932 public static final String RECEIVED_TIME = "receivedTime";
933
934 /**
935 * The time that the notification was created by the Calendar app,
936 * in UTC. (Useful for debugging missed reminders.)
937 * <P>Type: INTEGER (long; millis since epoch)</P>
938 */
939 public static final String NOTIFY_TIME = "notifyTime";
940
941 /**
942 * The state of this alert. It starts out as SCHEDULED, then when
943 * the alarm goes off, it changes to FIRED, and then when the user
944 * dismisses the alarm it changes to DISMISSED.
945 * <P>Type: INTEGER</P>
946 */
947 public static final String STATE = "state";
948
949 public static final int SCHEDULED = 0;
950 public static final int FIRED = 1;
951 public static final int DISMISSED = 2;
952
953 /**
954 * The number of minutes that this alarm precedes the start time
955 * <P>Type: INTEGER </P>
956 */
957 public static final String MINUTES = "minutes";
958
959 /**
960 * The default sort order for this table
961 */
962 public static final String DEFAULT_SORT_ORDER = "alarmTime ASC,begin ASC,title ASC";
963 }
964
965 public static final class CalendarAlerts implements BaseColumns,
966 CalendarAlertsColumns, EventsColumns, CalendarsColumns {
967 public static final String TABLE_NAME = "CalendarAlerts";
968 public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
969
970 /**
971 * This URI is for grouping the query results by event_id and begin
972 * time. This will return one result per instance of an event. So
973 * events with multiple alarms will appear just once, but multiple
974 * instances of a repeating event will show up multiple times.
975 */
976 public static final Uri CONTENT_URI_BY_INSTANCE =
977 Uri.parse("content://calendar/calendar_alerts/by_instance");
978
979 public static final Uri insert(ContentResolver cr, long eventId,
980 long begin, long end, long alarmTime, int minutes) {
981 ContentValues values = new ContentValues();
982 values.put(CalendarAlerts.EVENT_ID, eventId);
983 values.put(CalendarAlerts.BEGIN, begin);
984 values.put(CalendarAlerts.END, end);
985 values.put(CalendarAlerts.ALARM_TIME, alarmTime);
986 long currentTime = System.currentTimeMillis();
987 values.put(CalendarAlerts.CREATION_TIME, currentTime);
988 values.put(CalendarAlerts.RECEIVED_TIME, 0);
989 values.put(CalendarAlerts.NOTIFY_TIME, 0);
990 values.put(CalendarAlerts.STATE, SCHEDULED);
991 values.put(CalendarAlerts.MINUTES, minutes);
992 return cr.insert(CONTENT_URI, values);
993 }
994
995 public static final Cursor query(ContentResolver cr, String[] projection,
996 String selection, String[] selectionArgs) {
997 return cr.query(CONTENT_URI, projection, selection, selectionArgs,
998 DEFAULT_SORT_ORDER);
999 }
1000
1001 /**
1002 * Finds the next alarm after (or equal to) the given time and returns
1003 * the time of that alarm or -1 if no such alarm exists.
1004 *
1005 * @param cr the ContentResolver
1006 * @param millis the time in UTC milliseconds
1007 * @return the next alarm time greater than or equal to "millis", or -1
1008 * if no such alarm exists.
1009 */
1010 public static final long findNextAlarmTime(ContentResolver cr, long millis) {
1011 String selection = ALARM_TIME + ">=" + millis;
1012 // TODO: construct an explicit SQL query so that we can add
1013 // "LIMIT 1" to the end and get just one result.
1014 String[] projection = new String[] { ALARM_TIME };
1015 Cursor cursor = query(cr, projection, selection, null);
1016 long alarmTime = -1;
1017 try {
1018 if (cursor != null && cursor.moveToFirst()) {
1019 alarmTime = cursor.getLong(0);
1020 }
1021 } finally {
1022 if (cursor != null) {
1023 cursor.close();
1024 }
1025 }
1026 return alarmTime;
1027 }
1028
1029 /**
1030 * Searches the CalendarAlerts table for alarms that should have fired
1031 * but have not and then reschedules them. This method can be called
1032 * at boot time to restore alarms that may have been lost due to a
1033 * phone reboot.
1034 *
1035 * @param cr the ContentResolver
1036 * @param context the Context
1037 * @param manager the AlarmManager
1038 */
1039 public static final void rescheduleMissedAlarms(ContentResolver cr,
1040 Context context, AlarmManager manager) {
1041 // Get all the alerts that have been scheduled but have not fired
1042 // and should have fired by now and are not too old.
1043 long now = System.currentTimeMillis();
1044 long ancient = now - 24 * DateUtils.HOUR_IN_MILLIS;
1045 String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED
1046 + " AND " + CalendarAlerts.ALARM_TIME + "<" + now
1047 + " AND " + CalendarAlerts.ALARM_TIME + ">" + ancient
1048 + " AND " + CalendarAlerts.END + ">=" + now;
1049 String[] projection = new String[] {
1050 _ID,
1051 BEGIN,
1052 END,
1053 ALARM_TIME,
1054 };
1055 Cursor cursor = CalendarAlerts.query(cr, projection, selection, null);
1056 if (cursor == null) {
1057 return;
1058 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001059 if (Log.isLoggable(TAG, Log.DEBUG)) {
1060 Log.d(TAG, "missed alarms found: " + cursor.getCount());
1061 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062
1063 try {
1064 while (cursor.moveToNext()) {
1065 long id = cursor.getLong(0);
1066 long begin = cursor.getLong(1);
1067 long end = cursor.getLong(2);
1068 long alarmTime = cursor.getLong(3);
1069 Uri uri = ContentUris.withAppendedId(CONTENT_URI, id);
1070 Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
1071 intent.setData(uri);
1072 intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, begin);
1073 intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end);
1074 PendingIntent sender = PendingIntent.getBroadcast(context,
1075 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
The Android Open Source Project10592532009-03-18 17:39:46 -07001076 Log.w(TAG, "rescheduling missed alarm, id: " + id + " begin: " + begin
1077 + " end: " + end + " alarmTime: " + alarmTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
1079 }
1080 } finally {
1081 cursor.close();
1082 }
1083
1084 }
1085
1086 /**
1087 * Searches for an entry in the CalendarAlerts table that matches
1088 * the given event id, begin time and alarm time. If one is found
1089 * then this alarm already exists and this method returns true.
1090 *
1091 * @param cr the ContentResolver
1092 * @param eventId the event id to match
1093 * @param begin the start time of the event in UTC millis
1094 * @param alarmTime the alarm time of the event in UTC millis
1095 * @return true if there is already an alarm for the given event
1096 * with the same start time and alarm time.
1097 */
1098 public static final boolean alarmExists(ContentResolver cr, long eventId,
1099 long begin, long alarmTime) {
1100 String selection = CalendarAlerts.EVENT_ID + "=" + eventId
1101 + " AND " + CalendarAlerts.BEGIN + "=" + begin
1102 + " AND " + CalendarAlerts.ALARM_TIME + "=" + alarmTime;
1103 // TODO: construct an explicit SQL query so that we can add
1104 // "LIMIT 1" to the end and get just one result.
1105 String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
1106 Cursor cursor = query(cr, projection, selection, null);
1107 boolean found = false;
1108 try {
1109 if (cursor != null && cursor.getCount() > 0) {
1110 found = true;
1111 }
1112 } finally {
1113 if (cursor != null) {
1114 cursor.close();
1115 }
1116 }
1117 return found;
1118 }
1119 }
1120
1121 public interface ExtendedPropertiesColumns {
1122 /**
1123 * The event the extended property belongs to
1124 * <P>Type: INTEGER (foreign key to the Events table)</P>
1125 */
1126 public static final String EVENT_ID = "event_id";
1127
1128 /**
1129 * The name of the extended property. This is a uri of the form
1130 * {scheme}#{local-name} convention.
1131 * <P>Type: TEXT</P>
1132 */
1133 public static final String NAME = "name";
1134
1135 /**
1136 * The value of the extended property.
1137 * <P>Type: TEXT</P>
1138 */
1139 public static final String VALUE = "value";
1140 }
1141
1142 public static final class ExtendedProperties implements BaseColumns,
1143 ExtendedPropertiesColumns, EventsColumns {
1144 public static final Uri CONTENT_URI =
1145 Uri.parse("content://calendar/extendedproperties");
1146
1147 // TODO: fill out this class when we actually start utilizing extendedproperties
1148 // in the calendar application.
1149 }
1150}