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