Home | History | Annotate | Download | only in provider
      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 
    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          */
    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          */
    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 
    608         public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
    609             return new EntityIteratorImpl(cursor, resolver);
    610         }
    611 
    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 
    651             public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
    652                 super(cursor);
    653                 mResolver = resolver;
    654                 mProvider = null;
    655             }
    656 
    657             public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
    658                 super(cursor);
    659                 mResolver = null;
    660                 mProvider = provider;
    661             }
    662 
    663             @Override
    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 
    810         public static final Cursor query(ContentResolver cr, String[] projection) {
    811             return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
    812         }
    813 
    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 
    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 
    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 
    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          */
   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 
   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 
   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          */
   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          */
   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 
   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          */
   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          */
   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