• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.provider;
18 
19 import android.annotation.NonNull;
20 import android.annotation.SdkConstant;
21 import android.annotation.SdkConstant.SdkConstantType;
22 import android.annotation.TestApi;
23 import android.app.Activity;
24 import android.app.AlarmManager;
25 import android.app.PendingIntent;
26 import android.app.admin.DevicePolicyManager;
27 import android.compat.annotation.UnsupportedAppUsage;
28 import android.content.ComponentName;
29 import android.content.ContentProviderClient;
30 import android.content.ContentResolver;
31 import android.content.ContentUris;
32 import android.content.ContentValues;
33 import android.content.Context;
34 import android.content.CursorEntityIterator;
35 import android.content.Entity;
36 import android.content.EntityIterator;
37 import android.content.Intent;
38 import android.database.Cursor;
39 import android.database.DatabaseUtils;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.os.RemoteException;
43 import android.os.StrictMode;
44 import android.text.format.DateUtils;
45 import android.text.format.TimeMigrationUtils;
46 import android.util.Log;
47 
48 import com.android.internal.util.Preconditions;
49 
50 import java.util.Set;
51 
52 /**
53  * <p>
54  * The contract between the calendar provider and applications. Contains
55  * definitions for the supported URIs and data columns.
56  * </p>
57  * <h3>Overview</h3>
58  * <p>
59  * CalendarContract defines the data model of calendar and event related
60  * information. This data is stored in a number of tables:
61  * </p>
62  * <ul>
63  * <li>The {@link Calendars} table holds the calendar specific information. Each
64  * row in this table contains the details for a single calendar, such as the
65  * name, color, sync info, etc.</li>
66  * <li>The {@link Events} table holds the event specific information. Each row
67  * in this table has the info for a single event. It contains information such
68  * as event title, location, start time, end time, etc. The event can occur
69  * one-time or can recur multiple times. Attendees, reminders, and extended
70  * properties are stored on separate tables and reference the {@link Events#_ID}
71  * to link them with the event.</li>
72  * <li>The {@link Instances} table holds the start and end time for occurrences
73  * of an event. Each row in this table represents a single occurrence. For
74  * one-time events there will be a 1:1 mapping of instances to events. For
75  * recurring events, multiple rows will automatically be generated which
76  * correspond to multiple occurrences of that event.</li>
77  * <li>The {@link Attendees} table holds the event attendee or guest
78  * information. Each row represents a single guest of an event. It specifies the
79  * type of guest they are and their attendance response for the event.</li>
80  * <li>The {@link Reminders} table holds the alert/notification data. Each row
81  * represents a single alert for an event. An event can have multiple reminders.
82  * The number of reminders per event is specified in
83  * {@link Calendars#MAX_REMINDERS} which is set by the Sync Adapter that owns
84  * the given calendar. Reminders are specified in minutes before the event and
85  * have a type.</li>
86  * <li>The {@link ExtendedProperties} table holds opaque data fields used by the
87  * sync adapter. The provider takes no action with items in this table except to
88  * delete them when their related events are deleted.</li>
89  * </ul>
90  * <p>
91  * Other tables include:
92  * </p>
93  * <ul>
94  * <li>
95  * {@link SyncState}, which contains free-form data maintained by the sync
96  * adapters</li>
97  * </ul>
98  *
99  */
100 public final class CalendarContract {
101     private static final String TAG = "Calendar";
102 
103     /**
104      * Broadcast Action: This is the intent that gets fired when an alarm
105      * notification needs to be posted for a reminder.
106      *
107      */
108     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
109     public static final String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER";
110 
111     /**
112      * Activity Action: Display the event to the user in the custom app as
113      * specified in {@link EventsColumns#CUSTOM_APP_PACKAGE}. The custom app
114      * will be started via {@link Activity#startActivityForResult(Intent, int)}
115      * and it should call {@link Activity#setResult(int)} with
116      * {@link Activity#RESULT_OK} or {@link Activity#RESULT_CANCELED} to
117      * acknowledge whether the action was handled or not.
118      *
119      * The custom app should have an intent filter like the following:
120      * <pre>
121      * &lt;intent-filter&gt;
122      *    &lt;action android:name="android.provider.calendar.action.HANDLE_CUSTOM_EVENT" /&gt;
123      *    &lt;category android:name="android.intent.category.DEFAULT" /&gt;
124      *    &lt;data android:mimeType="vnd.android.cursor.item/event" /&gt;
125      * &lt;/intent-filter&gt;</pre>
126      * <p>
127      * Input: {@link Intent#getData} has the event URI. The extra
128      * {@link #EXTRA_EVENT_BEGIN_TIME} has the start time of the instance. The
129      * extra {@link #EXTRA_CUSTOM_APP_URI} will have the
130      * {@link EventsColumns#CUSTOM_APP_URI}.
131      * <p>
132      * Output: {@link Activity#RESULT_OK} if this was handled; otherwise
133      * {@link Activity#RESULT_CANCELED}.
134      */
135     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
136     public static final String ACTION_HANDLE_CUSTOM_EVENT =
137         "android.provider.calendar.action.HANDLE_CUSTOM_EVENT";
138 
139     /**
140      * Action used to help apps show calendar events in the managed profile.
141      */
142     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
143     public static final String ACTION_VIEW_MANAGED_PROFILE_CALENDAR_EVENT =
144             "android.provider.calendar.action.VIEW_MANAGED_PROFILE_CALENDAR_EVENT";
145 
146     /**
147      * Intent Extras key: {@link EventsColumns#CUSTOM_APP_URI} for the event in
148      * the {@link #ACTION_HANDLE_CUSTOM_EVENT} intent
149      */
150     public static final String EXTRA_CUSTOM_APP_URI = "customAppUri";
151 
152     /**
153      * Intent Extras key: The start time of an event or an instance of a
154      * recurring event. (milliseconds since epoch)
155      */
156     public static final String EXTRA_EVENT_BEGIN_TIME = "beginTime";
157 
158     /**
159      * Intent Extras key: The end time of an event or an instance of a recurring
160      * event. (milliseconds since epoch)
161      */
162     public static final String EXTRA_EVENT_END_TIME = "endTime";
163 
164     /**
165      * Intent Extras key: When creating an event, set this to true to create an
166      * all-day event by default
167      */
168     public static final String EXTRA_EVENT_ALL_DAY = "allDay";
169 
170     /**
171      * Intent Extras key: An extra of type {@code long} holding the id of an event.
172      */
173     public static final String EXTRA_EVENT_ID = "id";
174 
175     /**
176      * This authority is used for writing to or querying from the calendar
177      * provider. Note: This is set at first run and cannot be changed without
178      * breaking apps that access the provider.
179      */
180     public static final String AUTHORITY = "com.android.calendar";
181 
182     /**
183      * The content:// style URL for the top-level calendar authority
184      */
185     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
186 
187     /**
188      * The content:// style URL for the top-level cross-profile calendar uris.
189      * {@link android.database.ContentObserver} for this URL in the primary profile will be
190      * notified when there is a change in the managed profile calendar provider.
191      *
192      * <p>Throw UnsupportedOperationException if another profile doesn't exist or is disabled, or
193      * if the calling package is not allowlisted to access cross-profile calendar, or if the
194      * feature has been disabled by the user in Settings.
195      *
196      * @see Events#ENTERPRISE_CONTENT_URI
197      * @see Calendars#ENTERPRISE_CONTENT_URI
198      * @see Instances#ENTERPRISE_CONTENT_URI
199      * @see Instances#ENTERPRISE_CONTENT_BY_DAY_URI
200      * @see Instances#ENTERPRISE_CONTENT_SEARCH_URI
201      * @see Instances#ENTERPRISE_CONTENT_SEARCH_BY_DAY_URI
202      * @hide
203      */
204     public static final Uri ENTERPRISE_CONTENT_URI = Uri.parse(
205             "content://" + AUTHORITY + "/enterprise");
206 
207     /**
208      * An optional insert, update or delete URI parameter that allows the caller
209      * to specify that it is a sync adapter. The default value is false. If set
210      * to true, the modified row is not marked as "dirty" (needs to be synced)
211      * and when the provider calls
212      * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
213      * , the third parameter "syncToNetwork" is set to false. Furthermore, if
214      * set to true, the caller must also include
215      * {@link Calendars#ACCOUNT_NAME} and {@link Calendars#ACCOUNT_TYPE} as
216      * query parameters.
217      *
218      * @see Uri.Builder#appendQueryParameter(java.lang.String, java.lang.String)
219      */
220     public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
221 
222     /**
223      * A special account type for calendars not associated with any account.
224      * Normally calendars that do not match an account on the device will be
225      * removed. Setting the account_type on a calendar to this will prevent it
226      * from being wiped if it does not match an existing account.
227      *
228      * @see SyncColumns#ACCOUNT_TYPE
229      */
230     public static final String ACCOUNT_TYPE_LOCAL = "LOCAL";
231 
232     /**
233      * This utility class cannot be instantiated
234      */
CalendarContract()235     private CalendarContract() {}
236 
237     /**
238      * Starts an activity to view calendar events in the managed profile.
239      *
240      * When this API is called, the system will attempt to start an activity
241      * in the managed profile with an intent targeting the same caller package.
242      * The intent will have its action set to
243      * {@link CalendarContract#ACTION_VIEW_MANAGED_PROFILE_CALENDAR_EVENT} and contain extras
244      * corresponding to the API's arguments. A calendar app intending to support
245      * cross-profile events viewing should handle this intent, parse the arguments
246      * and show the appropriate UI.
247      *
248      * @param context the context.
249      * @param eventId the id of the event to be viewed. Will be put into {@link #EXTRA_EVENT_ID}
250      *                field of the intent.
251      * @param startMs the start time of the event in milliseconds since epoch.
252      *                Will be put into {@link #EXTRA_EVENT_BEGIN_TIME} field of the intent.
253      * @param endMs the end time of the event in milliseconds since epoch.
254      *              Will be put into {@link #EXTRA_EVENT_END_TIME} field of the intent.
255      * @param allDay if the event is an all-day event. Will be put into
256      *               {@link #EXTRA_EVENT_ALL_DAY} field of the intent.
257      * @param flags flags to be set on the intent via {@link Intent#setFlags}
258      * @return {@code true} if the activity is started successfully. {@code false} otherwise.
259      *
260      * @see #EXTRA_EVENT_ID
261      * @see #EXTRA_EVENT_BEGIN_TIME
262      * @see #EXTRA_EVENT_END_TIME
263      * @see #EXTRA_EVENT_ALL_DAY
264      */
startViewCalendarEventInManagedProfile(@onNull Context context, long eventId, long startMs, long endMs, boolean allDay, int flags)265     public static boolean startViewCalendarEventInManagedProfile(@NonNull Context context,
266             long eventId, long startMs, long endMs, boolean allDay, int flags) {
267         Preconditions.checkNotNull(context, "Context is null");
268         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
269                 Context.DEVICE_POLICY_SERVICE);
270         return dpm.startViewCalendarEventInManagedProfile(eventId, startMs,
271                 endMs, allDay, flags);
272     }
273 
274     /**
275      * Generic columns for use by sync adapters. The specific functions of these
276      * columns are private to the sync adapter. Other clients of the API should
277      * not attempt to either read or write this column. These columns are
278      * editable as part of the Calendars Uri, but can only be read if accessed
279      * through any other Uri.
280      */
281     protected interface CalendarSyncColumns {
282 
283 
284         /**
285          * Generic column for use by sync adapters. Column name.
286          * <P>Type: TEXT</P>
287          */
288         public static final String CAL_SYNC1 = "cal_sync1";
289 
290         /**
291          * Generic column for use by sync adapters. Column name.
292          * <P>Type: TEXT</P>
293          */
294         public static final String CAL_SYNC2 = "cal_sync2";
295 
296         /**
297          * Generic column for use by sync adapters. Column name.
298          * <P>Type: TEXT</P>
299          */
300         public static final String CAL_SYNC3 = "cal_sync3";
301 
302         /**
303          * Generic column for use by sync adapters. Column name.
304          * <P>Type: TEXT</P>
305          */
306         public static final String CAL_SYNC4 = "cal_sync4";
307 
308         /**
309          * Generic column for use by sync adapters. Column name.
310          * <P>Type: TEXT</P>
311          */
312         public static final String CAL_SYNC5 = "cal_sync5";
313 
314         /**
315          * Generic column for use by sync adapters. Column name.
316          * <P>Type: TEXT</P>
317          */
318         public static final String CAL_SYNC6 = "cal_sync6";
319 
320         /**
321          * Generic column for use by sync adapters. Column name.
322          * <P>Type: TEXT</P>
323          */
324         public static final String CAL_SYNC7 = "cal_sync7";
325 
326         /**
327          * Generic column for use by sync adapters. Column name.
328          * <P>Type: TEXT</P>
329          */
330         public static final String CAL_SYNC8 = "cal_sync8";
331 
332         /**
333          * Generic column for use by sync adapters. Column name.
334          * <P>Type: TEXT</P>
335          */
336         public static final String CAL_SYNC9 = "cal_sync9";
337 
338         /**
339          * Generic column for use by sync adapters. Column name.
340          * <P>Type: TEXT</P>
341          */
342         public static final String CAL_SYNC10 = "cal_sync10";
343     }
344 
345     /**
346      * Columns for Sync information used by Calendars and Events tables. These
347      * have specific uses which are expected to be consistent by the app and
348      * sync adapter.
349      *
350      */
351     protected interface SyncColumns extends CalendarSyncColumns {
352         /**
353          * The account that was used to sync the entry to the device. If the
354          * account_type is not {@link #ACCOUNT_TYPE_LOCAL} then the name and
355          * type must match an account on the device or the calendar will be
356          * deleted.
357          * <P>Type: TEXT</P>
358          */
359         public static final String ACCOUNT_NAME = "account_name";
360 
361         /**
362          * The type of the account that was used to sync the entry to the
363          * device. A type of {@link #ACCOUNT_TYPE_LOCAL} will keep this event
364          * form being deleted if there are no matching accounts on the device.
365          * <P>Type: TEXT</P>
366          */
367         public static final String ACCOUNT_TYPE = "account_type";
368 
369         /**
370          * The unique ID for a row assigned by the sync source. NULL if the row
371          * has never been synced. This is used as a reference id for exceptions
372          * along with {@link BaseColumns#_ID}.
373          * <P>Type: TEXT</P>
374          */
375         public static final String _SYNC_ID = "_sync_id";
376 
377         /**
378          * Used to indicate that local, unsynced, changes are present.
379          * <P>Type: INTEGER (long)</P>
380          */
381 
382         public static final String DIRTY = "dirty";
383 
384         /**
385          * Used in conjunction with {@link #DIRTY} to indicate what packages wrote local changes.
386          * <P>Type: TEXT</P>
387          */
388         public static final String MUTATORS = "mutators";
389 
390         /**
391          * Whether the row has been deleted but not synced to the server. A
392          * deleted row should be ignored.
393          * <P>
394          * Type: INTEGER (boolean)
395          * </P>
396          */
397         public static final String DELETED = "deleted";
398 
399         /**
400          * If set to 1 this causes events on this calendar to be duplicated with
401          * {@link Events#LAST_SYNCED} set to 1 whenever the event
402          * transitions from non-dirty to dirty. The duplicated event will not be
403          * expanded in the instances table and will only show up in sync adapter
404          * queries of the events table. It will also be deleted when the
405          * originating event has its dirty flag cleared by the sync adapter.
406          * <P>Type: INTEGER (boolean)</P>
407          */
408         public static final String CAN_PARTIALLY_UPDATE = "canPartiallyUpdate";
409     }
410 
411     /**
412      * Columns specific to the Calendars Uri that other Uris can query.
413      */
414     protected interface CalendarColumns {
415         /**
416          * The color of the calendar. This should only be updated by the sync
417          * adapter, not other apps, as changing a calendar's color can adversely
418          * affect its display.
419          * <P>Type: INTEGER (color value)</P>
420          */
421         public static final String CALENDAR_COLOR = "calendar_color";
422 
423         /**
424          * A key for looking up a color from the {@link Colors} table. NULL or
425          * an empty string are reserved for indicating that the calendar does
426          * not use a key for looking up the color. The provider will update
427          * {@link #CALENDAR_COLOR} automatically when a valid key is written to
428          * this column. The key must reference an existing row of the
429          * {@link Colors} table. @see Colors
430          * <P>
431          * Type: TEXT
432          * </P>
433          */
434         public static final String CALENDAR_COLOR_KEY = "calendar_color_index";
435 
436         /**
437          * The display name of the calendar. Column name.
438          * <P>
439          * Type: TEXT
440          * </P>
441          */
442         public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName";
443 
444         /**
445          * The level of access that the user has for the calendar
446          * <P>Type: INTEGER (one of the values below)</P>
447          */
448         public static final String CALENDAR_ACCESS_LEVEL = "calendar_access_level";
449 
450         /** Cannot access the calendar */
451         public static final int CAL_ACCESS_NONE = 0;
452         /** Can only see free/busy information about the calendar */
453         public static final int CAL_ACCESS_FREEBUSY = 100;
454         /** Can read all event details */
455         public static final int CAL_ACCESS_READ = 200;
456         /** Can reply yes/no/maybe to an event */
457         public static final int CAL_ACCESS_RESPOND = 300;
458         /** not used */
459         public static final int CAL_ACCESS_OVERRIDE = 400;
460         /** Full access to modify the calendar, but not the access control
461          * settings
462          */
463         public static final int CAL_ACCESS_CONTRIBUTOR = 500;
464         /** Full access to modify the calendar, but not the access control
465          * settings
466          */
467         public static final int CAL_ACCESS_EDITOR = 600;
468         /** Full access to the calendar */
469         public static final int CAL_ACCESS_OWNER = 700;
470         /** Domain admin */
471         public static final int CAL_ACCESS_ROOT = 800;
472 
473         /**
474          * Is the calendar selected to be displayed?
475          * 0 - do not show events associated with this calendar.
476          * 1 - show events associated with this calendar
477          * <P>Type: INTEGER (boolean)</P>
478          */
479         public static final String VISIBLE = "visible";
480 
481         /**
482          * The time zone the calendar is associated with.
483          * <P>Type: TEXT</P>
484          */
485         public static final String CALENDAR_TIME_ZONE = "calendar_timezone";
486 
487         /**
488          * Is this calendar synced and are its events stored on the device?
489          * 0 - Do not sync this calendar or store events for this calendar.
490          * 1 - Sync down events for this calendar.
491          * <p>Type: INTEGER (boolean)</p>
492          */
493         public static final String SYNC_EVENTS = "sync_events";
494 
495         /**
496          * The owner account for this calendar, based on the calendar feed.
497          * This will be different from the _SYNC_ACCOUNT for delegated calendars.
498          * Column name.
499          * <P>Type: String</P>
500          */
501         public static final String OWNER_ACCOUNT = "ownerAccount";
502 
503         /**
504          * Can the organizer respond to the event?  If no, the status of the
505          * organizer should not be shown by the UI.  Defaults to 1. Column name.
506          * <P>Type: INTEGER (boolean)</P>
507          */
508         public static final String CAN_ORGANIZER_RESPOND = "canOrganizerRespond";
509 
510         /**
511          * Can the organizer modify the time zone of the event? Column name.
512          * <P>Type: INTEGER (boolean)</P>
513         */
514         public static final String CAN_MODIFY_TIME_ZONE = "canModifyTimeZone";
515 
516         /**
517          * The maximum number of reminders allowed for an event. Column name.
518          * <P>Type: INTEGER</P>
519          */
520         public static final String MAX_REMINDERS = "maxReminders";
521 
522         /**
523          * A comma separated list of reminder methods supported for this
524          * calendar in the format "#,#,#". Valid types are
525          * {@link Reminders#METHOD_DEFAULT}, {@link Reminders#METHOD_ALERT},
526          * {@link Reminders#METHOD_EMAIL}, {@link Reminders#METHOD_SMS},
527          * {@link Reminders#METHOD_ALARM}. Column name.
528          * <P>Type: TEXT</P>
529          */
530         public static final String ALLOWED_REMINDERS = "allowedReminders";
531 
532         /**
533          * A comma separated list of availability types supported for this
534          * calendar in the format "#,#,#". Valid types are
535          * {@link Events#AVAILABILITY_BUSY}, {@link Events#AVAILABILITY_FREE},
536          * {@link Events#AVAILABILITY_TENTATIVE}. Setting this field to only
537          * {@link Events#AVAILABILITY_BUSY} should be used to indicate that
538          * changing the availability is not supported.
539          *
540          */
541         public static final String ALLOWED_AVAILABILITY = "allowedAvailability";
542 
543         /**
544          * A comma separated list of attendee types supported for this calendar
545          * in the format "#,#,#". Valid types are {@link Attendees#TYPE_NONE},
546          * {@link Attendees#TYPE_OPTIONAL}, {@link Attendees#TYPE_REQUIRED},
547          * {@link Attendees#TYPE_RESOURCE}. Setting this field to only
548          * {@link Attendees#TYPE_NONE} should be used to indicate that changing
549          * the attendee type is not supported.
550          *
551          */
552         public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes";
553 
554         /**
555          * Is this the primary calendar for this account. If this column is not explicitly set, the
556          * provider will return 1 if {@link Calendars#ACCOUNT_NAME} is equal to
557          * {@link Calendars#OWNER_ACCOUNT}.
558          */
559         public static final String IS_PRIMARY = "isPrimary";
560     }
561 
562     /**
563      * Class that represents a Calendar Entity. There is one entry per calendar.
564      * This is a helper class to make batch operations easier.
565      */
566     public static final class CalendarEntity implements BaseColumns, SyncColumns, CalendarColumns {
567 
568         /**
569          * The default Uri used when creating a new calendar EntityIterator.
570          */
571         @SuppressWarnings("hiding")
572         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
573                 "/calendar_entities");
574 
575         /**
576          * This utility class cannot be instantiated
577          */
CalendarEntity()578         private CalendarEntity() {}
579 
580         /**
581          * Creates an entity iterator for the given cursor. It assumes the
582          * cursor contains a calendars query.
583          *
584          * @param cursor query on {@link #CONTENT_URI}
585          * @return an EntityIterator of calendars
586          */
newEntityIterator(Cursor cursor)587         public static EntityIterator newEntityIterator(Cursor cursor) {
588             return new EntityIteratorImpl(cursor);
589         }
590 
591         private static class EntityIteratorImpl extends CursorEntityIterator {
592 
EntityIteratorImpl(Cursor cursor)593             public EntityIteratorImpl(Cursor cursor) {
594                 super(cursor);
595             }
596 
597             @Override
getEntityAndIncrementCursor(Cursor cursor)598             public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
599                 // we expect the cursor is already at the row we need to read from
600                 final long calendarId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
601 
602                 // Create the content value
603                 ContentValues cv = new ContentValues();
604                 cv.put(_ID, calendarId);
605 
606                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
607                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
608 
609                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
610                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
611                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
612 
613                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
614                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
615                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
616                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
617                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
618                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
619                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
620                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
621                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
622                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
623 
624                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
625                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
626                         Calendars.CALENDAR_DISPLAY_NAME);
627                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
628                         Calendars.CALENDAR_COLOR);
629                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
630                         Calendars.CALENDAR_COLOR_KEY);
631                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ACCESS_LEVEL);
632                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
633                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
634                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
635                         Calendars.CALENDAR_LOCATION);
636                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CALENDAR_TIME_ZONE);
637                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
638                         Calendars.OWNER_ACCOUNT);
639                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
640                         Calendars.CAN_ORGANIZER_RESPOND);
641                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
642                         Calendars.CAN_MODIFY_TIME_ZONE);
643                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
644                         Calendars.MAX_REMINDERS);
645                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
646                         Calendars.CAN_PARTIALLY_UPDATE);
647                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
648                         Calendars.ALLOWED_REMINDERS);
649 
650                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
651 
652                 // Create the Entity from the ContentValue
653                 Entity entity = new Entity(cv);
654 
655                 // Set cursor to next row
656                 cursor.moveToNext();
657 
658                 // Return the created Entity
659                 return entity;
660             }
661         }
662      }
663 
664     /**
665      * Constants and helpers for the Calendars table, which contains details for
666      * individual calendars. <h3>Operations</h3> All operations can be done
667      * either as an app or as a sync adapter. To perform an operation as a sync
668      * adapter {@link #CALLER_IS_SYNCADAPTER} should be set to true and
669      * {@link #ACCOUNT_NAME} and {@link #ACCOUNT_TYPE} must be set in the Uri
670      * parameters. See
671      * {@link Uri.Builder#appendQueryParameter(java.lang.String, java.lang.String)}
672      * for details on adding parameters. Sync adapters have write access to more
673      * columns but are restricted to a single account at a time. Calendars are
674      * designed to be primarily managed by a sync adapter and inserting new
675      * calendars should be done as a sync adapter. For the most part, apps
676      * should only update calendars (such as changing the color or display
677      * name). If a local calendar is required an app can do so by inserting as a
678      * sync adapter and using an {@link #ACCOUNT_TYPE} of
679      * {@link #ACCOUNT_TYPE_LOCAL} .
680      * <dl>
681      * <dt><b>Insert</b></dt>
682      * <dd>When inserting a new calendar the following fields must be included:
683      * <ul>
684      * <li>{@link #ACCOUNT_NAME}</li>
685      * <li>{@link #ACCOUNT_TYPE}</li>
686      * <li>{@link #NAME}</li>
687      * <li>{@link #CALENDAR_DISPLAY_NAME}</li>
688      * <li>{@link #CALENDAR_COLOR}</li>
689      * <li>{@link #CALENDAR_ACCESS_LEVEL}</li>
690      * <li>{@link #OWNER_ACCOUNT}</li>
691      * </ul>
692      * The following fields are not required when inserting a Calendar but are
693      * generally a good idea to include:
694      * <ul>
695      * <li>{@link #SYNC_EVENTS} set to 1</li>
696      * <li>{@link #CALENDAR_TIME_ZONE}</li>
697      * <li>{@link #ALLOWED_REMINDERS}</li>
698      * <li>{@link #ALLOWED_AVAILABILITY}</li>
699      * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li>
700      * </ul>
701      * <dt><b>Update</b></dt>
702      * <dd>To perform an update on a calendar the {@link #_ID} of the calendar
703      * should be provided either as an appended id to the Uri (
704      * {@link ContentUris#withAppendedId}) or as the first selection item--the
705      * selection should start with "_id=?" and the first selectionArg should be
706      * the _id of the calendar. Calendars may also be updated using a selection
707      * without the id. In general, the {@link #ACCOUNT_NAME} and
708      * {@link #ACCOUNT_TYPE} should not be changed after a calendar is created
709      * as this can cause issues for sync adapters.
710      * <dt><b>Delete</b></dt>
711      * <dd>Calendars can be deleted either by the {@link #_ID} as an appended id
712      * on the Uri or using any standard selection. Deleting a calendar should
713      * generally be handled by a sync adapter as it will remove the calendar
714      * from the database and all associated data (aka events).</dd>
715      * <dt><b>Query</b></dt>
716      * <dd>Querying the Calendars table will get you all information about a set
717      * of calendars. There will be one row returned for each calendar that
718      * matches the query selection, or at most a single row if the {@link #_ID}
719      * is appended to the Uri.</dd>
720      * </dl>
721      * <h3>Calendar Columns</h3> The following Calendar columns are writable by
722      * both an app and a sync adapter.
723      * <ul>
724      * <li>{@link #NAME}</li>
725      * <li>{@link #CALENDAR_DISPLAY_NAME}</li>
726      * <li>{@link #VISIBLE}</li>
727      * <li>{@link #SYNC_EVENTS}</li>
728      * </ul>
729      * The following Calendars columns are writable only by a sync adapter
730      * <ul>
731      * <li>{@link #ACCOUNT_NAME}</li>
732      * <li>{@link #ACCOUNT_TYPE}</li>
733      * <li>{@link #CALENDAR_COLOR}</li>
734      * <li>{@link #_SYNC_ID}</li>
735      * <li>{@link #DIRTY}</li>
736      * <li>{@link #MUTATORS}</li>
737      * <li>{@link #OWNER_ACCOUNT}</li>
738      * <li>{@link #MAX_REMINDERS}</li>
739      * <li>{@link #ALLOWED_REMINDERS}</li>
740      * <li>{@link #ALLOWED_AVAILABILITY}</li>
741      * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li>
742      * <li>{@link #CAN_MODIFY_TIME_ZONE}</li>
743      * <li>{@link #CAN_ORGANIZER_RESPOND}</li>
744      * <li>{@link #CAN_PARTIALLY_UPDATE}</li>
745      * <li>{@link #CALENDAR_LOCATION}</li>
746      * <li>{@link #CALENDAR_TIME_ZONE}</li>
747      * <li>{@link #CALENDAR_ACCESS_LEVEL}</li>
748      * <li>{@link #DELETED}</li>
749      * <li>{@link #CAL_SYNC1}</li>
750      * <li>{@link #CAL_SYNC2}</li>
751      * <li>{@link #CAL_SYNC3}</li>
752      * <li>{@link #CAL_SYNC4}</li>
753      * <li>{@link #CAL_SYNC5}</li>
754      * <li>{@link #CAL_SYNC6}</li>
755      * <li>{@link #CAL_SYNC7}</li>
756      * <li>{@link #CAL_SYNC8}</li>
757      * <li>{@link #CAL_SYNC9}</li>
758      * <li>{@link #CAL_SYNC10}</li>
759      * </ul>
760      */
761     public static final class Calendars implements BaseColumns, SyncColumns, CalendarColumns {
762 
763         /**
764          * This utility class cannot be instantiated
765          */
Calendars()766         private Calendars() {}
767 
768         /**
769          * The content:// style URL for accessing Calendars
770          */
771         @SuppressWarnings("hiding")
772         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
773 
774         /**
775          * The content:// style URL for querying Calendars table in the managed profile. Appending
776          * a calendar id using {@link ContentUris#withAppendedId(Uri, long)} specifies
777          * a single calendar.
778          *
779          * <p>The following columns are allowed to be queried via this uri:
780          * <ul>
781          * <li>{@link #_ID}</li>
782          * <li>{@link #CALENDAR_COLOR}</li>
783          * <li>{@link #VISIBLE}</li>
784          * <li>{@link #CALENDAR_LOCATION}</li>
785          * <li>{@link #CALENDAR_TIME_ZONE}</li>
786          * <li>{@link #IS_PRIMARY}</li>
787          * </ul>
788          *
789          * <p>{@link IllegalArgumentException} is thrown if there exists columns in the
790          * projection of the query to this uri that are not contained in the above list.
791          *
792          * <p>This uri returns an empty cursor if the calling user is not a parent profile
793          * of a managed profile, or the managed profile is disabled, or cross-profile calendar is
794          * disabled in Settings, or this uri is queried from a package that is not allowed by
795          * the profile owner of the managed profile via
796          * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
797          *
798          * <p>Apps can register a {@link android.database.ContentObserver} for this URI to listen
799          * to changes.
800          *
801          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
802          */
803         @NonNull
804         public static final Uri ENTERPRISE_CONTENT_URI =
805                 Uri.parse("content://" + AUTHORITY + "/enterprise/calendars");
806 
807         /**
808          * The default sort order for this table
809          */
810         public static final String DEFAULT_SORT_ORDER = CALENDAR_DISPLAY_NAME;
811 
812         /**
813          * The name of the calendar. Column name.
814          * <P>Type: TEXT</P>
815          */
816         public static final String NAME = "name";
817 
818         /**
819          * The default location for the calendar. Column name.
820          * <P>Type: TEXT</P>
821          */
822         public static final String CALENDAR_LOCATION = "calendar_location";
823 
824         /**
825          * These fields are only writable by a sync adapter. To modify them the
826          * caller must include {@link #CALLER_IS_SYNCADAPTER},
827          * {@link #ACCOUNT_NAME}, and {@link #ACCOUNT_TYPE} in the Uri's query
828          * parameters. TODO move to provider
829          *
830          * @hide
831          */
832         @TestApi
833         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
834             ACCOUNT_NAME,
835             ACCOUNT_TYPE,
836             _SYNC_ID,
837             DIRTY,
838             MUTATORS,
839             OWNER_ACCOUNT,
840             MAX_REMINDERS,
841             ALLOWED_REMINDERS,
842             CAN_MODIFY_TIME_ZONE,
843             CAN_ORGANIZER_RESPOND,
844             CAN_PARTIALLY_UPDATE,
845             CALENDAR_LOCATION,
846             CALENDAR_TIME_ZONE,
847             CALENDAR_ACCESS_LEVEL,
848             DELETED,
849             CAL_SYNC1,
850             CAL_SYNC2,
851             CAL_SYNC3,
852             CAL_SYNC4,
853             CAL_SYNC5,
854             CAL_SYNC6,
855             CAL_SYNC7,
856             CAL_SYNC8,
857             CAL_SYNC9,
858             CAL_SYNC10,
859         };
860     }
861 
862     /**
863      * Columns from the Attendees table that other tables join into themselves.
864      */
865     protected interface AttendeesColumns {
866 
867         /**
868          * The id of the event. Column name.
869          * <P>Type: INTEGER</P>
870          */
871         public static final String EVENT_ID = "event_id";
872 
873         /**
874          * The name of the attendee. Column name.
875          * <P>Type: STRING</P>
876          */
877         public static final String ATTENDEE_NAME = "attendeeName";
878 
879         /**
880          * The email address of the attendee. Column name.
881          * <P>Type: STRING</P>
882          */
883         public static final String ATTENDEE_EMAIL = "attendeeEmail";
884 
885         /**
886          * The relationship of the attendee to the user. Column name.
887          * <P>Type: INTEGER (one of {@link #RELATIONSHIP_ATTENDEE}, ...}.</P>
888          */
889         public static final String ATTENDEE_RELATIONSHIP = "attendeeRelationship";
890 
891         public static final int RELATIONSHIP_NONE = 0;
892         public static final int RELATIONSHIP_ATTENDEE = 1;
893         public static final int RELATIONSHIP_ORGANIZER = 2;
894         public static final int RELATIONSHIP_PERFORMER = 3;
895         public static final int RELATIONSHIP_SPEAKER = 4;
896 
897         /**
898          * The type of attendee. Column name.
899          * <P>
900          * Type: Integer (one of {@link #TYPE_NONE}, {@link #TYPE_REQUIRED},
901          * {@link #TYPE_OPTIONAL}, {@link #TYPE_RESOURCE})
902          * </P>
903          */
904         public static final String ATTENDEE_TYPE = "attendeeType";
905 
906         public static final int TYPE_NONE = 0;
907         public static final int TYPE_REQUIRED = 1;
908         public static final int TYPE_OPTIONAL = 2;
909         /**
910          * This specifies that an attendee is a resource, like a room, a
911          * cabbage, or something and not an actual person.
912          */
913         public static final int TYPE_RESOURCE = 3;
914 
915         /**
916          * The attendance status of the attendee. Column name.
917          * <P>Type: Integer (one of {@link #ATTENDEE_STATUS_ACCEPTED}, ...).</P>
918          */
919         public static final String ATTENDEE_STATUS = "attendeeStatus";
920 
921         public static final int ATTENDEE_STATUS_NONE = 0;
922         public static final int ATTENDEE_STATUS_ACCEPTED = 1;
923         public static final int ATTENDEE_STATUS_DECLINED = 2;
924         public static final int ATTENDEE_STATUS_INVITED = 3;
925         public static final int ATTENDEE_STATUS_TENTATIVE = 4;
926 
927         /**
928          * The identity of the attendee as referenced in
929          * {@link ContactsContract.CommonDataKinds.Identity#IDENTITY}.
930          * This is required only if {@link #ATTENDEE_ID_NAMESPACE} is present. Column name.
931          * <P>Type: STRING</P>
932          */
933         public static final String ATTENDEE_IDENTITY = "attendeeIdentity";
934 
935         /**
936          * The identity name space of the attendee as referenced in
937          * {@link ContactsContract.CommonDataKinds.Identity#NAMESPACE}.
938          * This is required only if {@link #ATTENDEE_IDENTITY} is present. Column name.
939          * <P>Type: STRING</P>
940          */
941         public static final String ATTENDEE_ID_NAMESPACE = "attendeeIdNamespace";
942     }
943 
944     /**
945      * Fields and helpers for interacting with Attendees. Each row of this table
946      * represents a single attendee or guest of an event. Calling
947      * {@link #query(ContentResolver, long, String[])} will return a list of attendees for
948      * the event with the given eventId. Both apps and sync adapters may write
949      * to this table. There are six writable fields and all of them except
950      * {@link #ATTENDEE_NAME} must be included when inserting a new attendee.
951      * They are:
952      * <ul>
953      * <li>{@link #EVENT_ID}</li>
954      * <li>{@link #ATTENDEE_NAME}</li>
955      * <li>{@link #ATTENDEE_EMAIL}</li>
956      * <li>{@link #ATTENDEE_RELATIONSHIP}</li>
957      * <li>{@link #ATTENDEE_TYPE}</li>
958      * <li>{@link #ATTENDEE_STATUS}</li>
959      * <li>{@link #ATTENDEE_IDENTITY}</li>
960      * <li>{@link #ATTENDEE_ID_NAMESPACE}</li>
961      * </ul>
962      */
963     public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
964 
965         /**
966          * The content:// style URL for accessing Attendees data
967          */
968         @SuppressWarnings("hiding")
969         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
970         private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
971 
972         /**
973          * This utility class cannot be instantiated
974          */
Attendees()975         private Attendees() {}
976 
977         /**
978          * Queries all attendees associated with the given event. This is a
979          * blocking call and should not be done on the UI thread.
980          *
981          * @param cr The content resolver to use for the query
982          * @param eventId The id of the event to retrieve attendees for
983          * @param projection the columns to return in the cursor
984          * @return A Cursor containing all attendees for the event
985          */
query(ContentResolver cr, long eventId, String[] projection)986         public static final Cursor query(ContentResolver cr, long eventId, String[] projection) {
987             String[] attArgs = {Long.toString(eventId)};
988             return cr.query(CONTENT_URI, projection, ATTENDEES_WHERE, attArgs /* selection args */,
989                     null /* sort order */);
990         }
991     }
992 
993     /**
994      * Columns from the Events table that other tables join into themselves.
995      */
996     protected interface EventsColumns {
997 
998         /**
999          * The {@link Calendars#_ID} of the calendar the event belongs to.
1000          * Column name.
1001          * <P>Type: INTEGER</P>
1002          */
1003         public static final String CALENDAR_ID = "calendar_id";
1004 
1005         /**
1006          * The title of the event. Column name.
1007          * <P>Type: TEXT</P>
1008          */
1009         public static final String TITLE = "title";
1010 
1011         /**
1012          * The description of the event. Column name.
1013          * <P>Type: TEXT</P>
1014          */
1015         public static final String DESCRIPTION = "description";
1016 
1017         /**
1018          * Where the event takes place. Column name.
1019          * <P>Type: TEXT</P>
1020          */
1021         public static final String EVENT_LOCATION = "eventLocation";
1022 
1023         /**
1024          * A secondary color for the individual event. This should only be
1025          * updated by the sync adapter for a given account.
1026          * <P>Type: INTEGER</P>
1027          */
1028         public static final String EVENT_COLOR = "eventColor";
1029 
1030         /**
1031          * A secondary color key for the individual event. NULL or an empty
1032          * string are reserved for indicating that the event does not use a key
1033          * for looking up the color. The provider will update
1034          * {@link #EVENT_COLOR} automatically when a valid key is written to
1035          * this column. The key must reference an existing row of the
1036          * {@link Colors} table. @see Colors
1037          * <P>
1038          * Type: TEXT
1039          * </P>
1040          */
1041         public static final String EVENT_COLOR_KEY = "eventColor_index";
1042 
1043         /**
1044          * This will be {@link #EVENT_COLOR} if it is not null; otherwise, this will be
1045          * {@link Calendars#CALENDAR_COLOR}.
1046          * Read-only value. To modify, write to {@link #EVENT_COLOR} or
1047          * {@link Calendars#CALENDAR_COLOR} directly.
1048          *<P>
1049          *     Type: INTEGER
1050          *</P>
1051          */
1052         public static final String DISPLAY_COLOR = "displayColor";
1053 
1054         /**
1055          * The event status. Column name.
1056          * <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P>
1057          */
1058         public static final String STATUS = "eventStatus";
1059 
1060         public static final int STATUS_TENTATIVE = 0;
1061         public static final int STATUS_CONFIRMED = 1;
1062         public static final int STATUS_CANCELED = 2;
1063 
1064         /**
1065          * This is a copy of the attendee status for the owner of this event.
1066          * This field is copied here so that we can efficiently filter out
1067          * events that are declined without having to look in the Attendees
1068          * table. Column name.
1069          *
1070          * <P>Type: INTEGER (int)</P>
1071          */
1072         public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
1073 
1074         /**
1075          * This column is available for use by sync adapters. Column name.
1076          * <P>Type: TEXT</P>
1077          */
1078         public static final String SYNC_DATA1 = "sync_data1";
1079 
1080         /**
1081          * This column is available for use by sync adapters. Column name.
1082          * <P>Type: TEXT</P>
1083          */
1084         public static final String SYNC_DATA2 = "sync_data2";
1085 
1086         /**
1087          * This column is available for use by sync adapters. Column name.
1088          * <P>Type: TEXT</P>
1089          */
1090         public static final String SYNC_DATA3 = "sync_data3";
1091 
1092         /**
1093          * This column is available for use by sync adapters. Column name.
1094          * <P>Type: TEXT</P>
1095          */
1096         public static final String SYNC_DATA4 = "sync_data4";
1097 
1098         /**
1099          * This column is available for use by sync adapters. Column name.
1100          * <P>Type: TEXT</P>
1101          */
1102         public static final String SYNC_DATA5 = "sync_data5";
1103 
1104         /**
1105          * This column is available for use by sync adapters. Column name.
1106          * <P>Type: TEXT</P>
1107          */
1108         public static final String SYNC_DATA6 = "sync_data6";
1109 
1110         /**
1111          * This column is available for use by sync adapters. Column name.
1112          * <P>Type: TEXT</P>
1113          */
1114         public static final String SYNC_DATA7 = "sync_data7";
1115 
1116         /**
1117          * This column is available for use by sync adapters. Column name.
1118          * <P>Type: TEXT</P>
1119          */
1120         public static final String SYNC_DATA8 = "sync_data8";
1121 
1122         /**
1123          * This column is available for use by sync adapters. Column name.
1124          * <P>Type: TEXT</P>
1125          */
1126         public static final String SYNC_DATA9 = "sync_data9";
1127 
1128         /**
1129          * This column is available for use by sync adapters. Column name.
1130          * <P>Type: TEXT</P>
1131          */
1132         public static final String SYNC_DATA10 = "sync_data10";
1133 
1134         /**
1135          * Used to indicate that a row is not a real event but an original copy of a locally
1136          * modified event. A copy is made when an event changes from non-dirty to dirty and the
1137          * event is on a calendar with {@link Calendars#CAN_PARTIALLY_UPDATE} set to 1. This copy
1138          * does not get expanded in the instances table and is only visible in queries made by a
1139          * sync adapter. The copy gets removed when the event is changed back to non-dirty by a
1140          * sync adapter.
1141          * <P>Type: INTEGER (boolean)</P>
1142          */
1143         public static final String LAST_SYNCED = "lastSynced";
1144 
1145         /**
1146          * The time the event starts in UTC millis since epoch. Column name.
1147          * <P>Type: INTEGER (long; millis since epoch)</P>
1148          */
1149         public static final String DTSTART = "dtstart";
1150 
1151         /**
1152          * The time the event ends in UTC millis since epoch. Column name.
1153          * <P>Type: INTEGER (long; millis since epoch)</P>
1154          */
1155         public static final String DTEND = "dtend";
1156 
1157         /**
1158          * The duration of the event in RFC2445 format. Column name.
1159          * <P>Type: TEXT (duration in RFC2445 format)</P>
1160          */
1161         public static final String DURATION = "duration";
1162 
1163         /**
1164          * The timezone for the event. Column name.
1165          * <P>Type: TEXT</P>
1166          */
1167         public static final String EVENT_TIMEZONE = "eventTimezone";
1168 
1169         /**
1170          * The timezone for the end time of the event. Column name.
1171          * <P>Type: TEXT</P>
1172          */
1173         public static final String EVENT_END_TIMEZONE = "eventEndTimezone";
1174 
1175         /**
1176          * Is the event all day (time zone independent). Column name.
1177          * <P>Type: INTEGER (boolean)</P>
1178          */
1179         public static final String ALL_DAY = "allDay";
1180 
1181         /**
1182          * Defines how the event shows up for others when the calendar is
1183          * shared. Column name.
1184          * <P>Type: INTEGER (One of {@link #ACCESS_DEFAULT}, ...)</P>
1185          */
1186         public static final String ACCESS_LEVEL = "accessLevel";
1187 
1188         /**
1189          * Default access is controlled by the server and will be treated as
1190          * public on the device.
1191          */
1192         public static final int ACCESS_DEFAULT = 0;
1193         /**
1194          * Confidential is not used by the app.
1195          */
1196         public static final int ACCESS_CONFIDENTIAL = 1;
1197         /**
1198          * Private shares the event as a free/busy slot with no details.
1199          */
1200         public static final int ACCESS_PRIVATE = 2;
1201         /**
1202          * Public makes the contents visible to anyone with access to the
1203          * calendar.
1204          */
1205         public static final int ACCESS_PUBLIC = 3;
1206 
1207         /**
1208          * If this event counts as busy time or is still free time that can be
1209          * scheduled over. Column name.
1210          * <P>
1211          * Type: INTEGER (One of {@link #AVAILABILITY_BUSY},
1212          * {@link #AVAILABILITY_FREE}, {@link #AVAILABILITY_TENTATIVE})
1213          * </P>
1214          */
1215         public static final String AVAILABILITY = "availability";
1216 
1217         /**
1218          * Indicates that this event takes up time and will conflict with other
1219          * events.
1220          */
1221         public static final int AVAILABILITY_BUSY = 0;
1222         /**
1223          * Indicates that this event is free time and will not conflict with
1224          * other events.
1225          */
1226         public static final int AVAILABILITY_FREE = 1;
1227         /**
1228          * Indicates that the owner's availability may change, but should be
1229          * considered busy time that will conflict.
1230          */
1231         public static final int AVAILABILITY_TENTATIVE = 2;
1232 
1233         /**
1234          * Whether the event has an alarm or not. Column name.
1235          * <P>Type: INTEGER (boolean)</P>
1236          */
1237         public static final String HAS_ALARM = "hasAlarm";
1238 
1239         /**
1240          * Whether the event has extended properties or not. Column name.
1241          * <P>Type: INTEGER (boolean)</P>
1242          */
1243         public static final String HAS_EXTENDED_PROPERTIES = "hasExtendedProperties";
1244 
1245         /**
1246          * The recurrence rule for the event. Column name.
1247          * <P>Type: TEXT</P>
1248          */
1249         public static final String RRULE = "rrule";
1250 
1251         /**
1252          * The recurrence dates for the event. Column name.
1253          * <P>Type: TEXT</P>
1254          */
1255         public static final String RDATE = "rdate";
1256 
1257         /**
1258          * The recurrence exception rule for the event. Column name.
1259          * <P>Type: TEXT</P>
1260          */
1261         public static final String EXRULE = "exrule";
1262 
1263         /**
1264          * The recurrence exception dates for the event. Column name.
1265          * <P>Type: TEXT</P>
1266          */
1267         public static final String EXDATE = "exdate";
1268 
1269         /**
1270          * The {@link Events#_ID} of the original recurring event for which this
1271          * event is an exception. Column name.
1272          * <P>Type: TEXT</P>
1273          */
1274         public static final String ORIGINAL_ID = "original_id";
1275 
1276         /**
1277          * The _sync_id of the original recurring event for which this event is
1278          * an exception. The provider should keep the original_id in sync when
1279          * this is updated. Column name.
1280          * <P>Type: TEXT</P>
1281          */
1282         public static final String ORIGINAL_SYNC_ID = "original_sync_id";
1283 
1284         /**
1285          * The original instance time of the recurring event for which this
1286          * event is an exception. Column name.
1287          * <P>Type: INTEGER (long; millis since epoch)</P>
1288          */
1289         public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
1290 
1291         /**
1292          * The allDay status (true or false) of the original recurring event
1293          * for which this event is an exception. Column name.
1294          * <P>Type: INTEGER (boolean)</P>
1295          */
1296         public static final String ORIGINAL_ALL_DAY = "originalAllDay";
1297 
1298         /**
1299          * The last date this event repeats on, or NULL if it never ends. Column
1300          * name.
1301          * <P>Type: INTEGER (long; millis since epoch)</P>
1302          */
1303         public static final String LAST_DATE = "lastDate";
1304 
1305         /**
1306          * Whether the event has attendee information.  True if the event
1307          * has full attendee data, false if the event has information about
1308          * self only. Column name.
1309          * <P>Type: INTEGER (boolean)</P>
1310          */
1311         public static final String HAS_ATTENDEE_DATA = "hasAttendeeData";
1312 
1313         /**
1314          * Whether guests can modify the event. Column name.
1315          * <P>Type: INTEGER (boolean)</P>
1316          */
1317         public static final String GUESTS_CAN_MODIFY = "guestsCanModify";
1318 
1319         /**
1320          * Whether guests can invite other guests. Column name.
1321          * <P>Type: INTEGER (boolean)</P>
1322          */
1323         public static final String GUESTS_CAN_INVITE_OTHERS = "guestsCanInviteOthers";
1324 
1325         /**
1326          * Whether guests can see the list of attendees. Column name.
1327          * <P>Type: INTEGER (boolean)</P>
1328          */
1329         public static final String GUESTS_CAN_SEE_GUESTS = "guestsCanSeeGuests";
1330 
1331         /**
1332          * Email of the organizer (owner) of the event. Column name.
1333          * <P>Type: STRING</P>
1334          */
1335         public static final String ORGANIZER = "organizer";
1336 
1337         /**
1338          * Are we the organizer of this event. If this column is not explicitly set, the provider
1339          * will return 1 if {@link #ORGANIZER} is equal to {@link Calendars#OWNER_ACCOUNT}.
1340          * Column name.
1341          * <P>Type: STRING</P>
1342          */
1343         public static final String IS_ORGANIZER = "isOrganizer";
1344 
1345         /**
1346          * Whether the user can invite others to the event. The
1347          * GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary
1348          * guest, while CAN_INVITE_OTHERS indicates if the user can invite
1349          * others (either through GUESTS_CAN_INVITE_OTHERS or because the user
1350          * has modify access to the event). Column name.
1351          * <P>Type: INTEGER (boolean, readonly)</P>
1352          */
1353         public static final String CAN_INVITE_OTHERS = "canInviteOthers";
1354 
1355         /**
1356          * The package name of the custom app that can provide a richer
1357          * experience for the event. See the ACTION TYPE
1358          * {@link CalendarContract#ACTION_HANDLE_CUSTOM_EVENT} for details.
1359          * Column name.
1360          * <P> Type: TEXT </P>
1361          */
1362         public static final String CUSTOM_APP_PACKAGE = "customAppPackage";
1363 
1364         /**
1365          * The URI used by the custom app for the event. Column name.
1366          * <P>Type: TEXT</P>
1367          */
1368         public static final String CUSTOM_APP_URI = "customAppUri";
1369 
1370         /**
1371          * The UID for events added from the RFC 2445 iCalendar format.
1372          * Column name.
1373          * <P>Type: TEXT</P>
1374          */
1375         public static final String UID_2445 = "uid2445";
1376     }
1377 
1378     /**
1379      * Class that represents an Event Entity. There is one entry per event.
1380      * Recurring events show up as a single entry. This is a helper class to
1381      * make batch operations easier. A {@link ContentResolver} or
1382      * {@link ContentProviderClient} is required as the helper does additional
1383      * queries to add reminders and attendees to each entry.
1384      */
1385     public static final class EventsEntity implements BaseColumns, SyncColumns, EventsColumns {
1386         /**
1387          * The content:// style URL for this table
1388          */
1389         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1390                 "/event_entities");
1391 
1392         /**
1393          * This utility class cannot be instantiated
1394          */
EventsEntity()1395         private EventsEntity() {}
1396 
1397         /**
1398          * Creates a new iterator for events
1399          *
1400          * @param cursor An event query
1401          * @param resolver For performing additional queries
1402          * @return an EntityIterator containing one entity per event in the
1403          *         cursor
1404          */
newEntityIterator(Cursor cursor, ContentResolver resolver)1405         public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
1406             return new EntityIteratorImpl(cursor, resolver);
1407         }
1408 
1409         /**
1410          * Creates a new iterator for events
1411          *
1412          * @param cursor An event query
1413          * @param provider For performing additional queries
1414          * @return an EntityIterator containing one entity per event in the
1415          *         cursor
1416          */
newEntityIterator(Cursor cursor, ContentProviderClient provider)1417         public static EntityIterator newEntityIterator(Cursor cursor,
1418                 ContentProviderClient provider) {
1419             return new EntityIteratorImpl(cursor, provider);
1420         }
1421 
1422         private static class EntityIteratorImpl extends CursorEntityIterator {
1423             private final ContentResolver mResolver;
1424             private final ContentProviderClient mProvider;
1425 
1426             private static final String[] REMINDERS_PROJECTION = new String[] {
1427                     Reminders.MINUTES,
1428                     Reminders.METHOD,
1429             };
1430             private static final int COLUMN_MINUTES = 0;
1431             private static final int COLUMN_METHOD = 1;
1432 
1433             private static final String[] ATTENDEES_PROJECTION = new String[] {
1434                     Attendees.ATTENDEE_NAME,
1435                     Attendees.ATTENDEE_EMAIL,
1436                     Attendees.ATTENDEE_RELATIONSHIP,
1437                     Attendees.ATTENDEE_TYPE,
1438                     Attendees.ATTENDEE_STATUS,
1439                     Attendees.ATTENDEE_IDENTITY,
1440                     Attendees.ATTENDEE_ID_NAMESPACE
1441             };
1442             private static final int COLUMN_ATTENDEE_NAME = 0;
1443             private static final int COLUMN_ATTENDEE_EMAIL = 1;
1444             private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
1445             private static final int COLUMN_ATTENDEE_TYPE = 3;
1446             private static final int COLUMN_ATTENDEE_STATUS = 4;
1447             private static final int COLUMN_ATTENDEE_IDENTITY = 5;
1448             private static final int COLUMN_ATTENDEE_ID_NAMESPACE = 6;
1449 
1450             private static final String[] EXTENDED_PROJECTION = new String[] {
1451                     ExtendedProperties._ID,
1452                     ExtendedProperties.NAME,
1453                     ExtendedProperties.VALUE
1454             };
1455             private static final int COLUMN_ID = 0;
1456             private static final int COLUMN_NAME = 1;
1457             private static final int COLUMN_VALUE = 2;
1458 
1459             private static final String WHERE_EVENT_ID = "event_id=?";
1460 
EntityIteratorImpl(Cursor cursor, ContentResolver resolver)1461             public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
1462                 super(cursor);
1463                 mResolver = resolver;
1464                 mProvider = null;
1465             }
1466 
EntityIteratorImpl(Cursor cursor, ContentProviderClient provider)1467             public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
1468                 super(cursor);
1469                 mResolver = null;
1470                 mProvider = provider;
1471             }
1472 
1473             @Override
getEntityAndIncrementCursor(Cursor cursor)1474             public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
1475                 // we expect the cursor is already at the row we need to read from
1476                 final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
1477                 ContentValues cv = new ContentValues();
1478                 cv.put(Events._ID, eventId);
1479                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
1480                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
1481                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
1482                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
1483                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
1484                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
1485                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
1486                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
1487                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
1488                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
1489                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_END_TIMEZONE);
1490                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
1491                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
1492                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
1493                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EVENT_COLOR);
1494                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_COLOR_KEY);
1495                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
1496                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
1497                         HAS_EXTENDED_PROPERTIES);
1498                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
1499                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
1500                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
1501                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
1502                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_SYNC_ID);
1503                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_ID);
1504                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
1505                         ORIGINAL_INSTANCE_TIME);
1506                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
1507                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
1508                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
1509                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
1510                         GUESTS_CAN_INVITE_OTHERS);
1511                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
1512                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
1513                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_PACKAGE);
1514                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_URI);
1515                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, UID_2445);
1516                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
1517                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, IS_ORGANIZER);
1518                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
1519                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
1520                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
1521                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
1522                 DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
1523                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA1);
1524                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA2);
1525                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA3);
1526                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA4);
1527                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA5);
1528                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA6);
1529                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA7);
1530                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA8);
1531                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA9);
1532                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA10);
1533                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
1534                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
1535                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC3);
1536                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC4);
1537                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC5);
1538                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC6);
1539                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC7);
1540                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC8);
1541                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC9);
1542                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC10);
1543 
1544                 Entity entity = new Entity(cv);
1545                 Cursor subCursor;
1546                 if (mResolver != null) {
1547                     subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
1548                             WHERE_EVENT_ID,
1549                             new String[] { Long.toString(eventId) }  /* selectionArgs */,
1550                             null /* sortOrder */);
1551                 } else {
1552                     subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
1553                             WHERE_EVENT_ID,
1554                             new String[] { Long.toString(eventId) }  /* selectionArgs */,
1555                             null /* sortOrder */);
1556                 }
1557                 try {
1558                     while (subCursor.moveToNext()) {
1559                         ContentValues reminderValues = new ContentValues();
1560                         reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
1561                         reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
1562                         entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
1563                     }
1564                 } finally {
1565                     subCursor.close();
1566                 }
1567 
1568                 if (mResolver != null) {
1569                     subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
1570                             WHERE_EVENT_ID,
1571                             new String[] { Long.toString(eventId) } /* selectionArgs */,
1572                             null /* sortOrder */);
1573                 } else {
1574                     subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
1575                             WHERE_EVENT_ID,
1576                             new String[] { Long.toString(eventId) } /* selectionArgs */,
1577                             null /* sortOrder */);
1578                 }
1579                 try {
1580                     while (subCursor.moveToNext()) {
1581                         ContentValues attendeeValues = new ContentValues();
1582                         attendeeValues.put(Attendees.ATTENDEE_NAME,
1583                                 subCursor.getString(COLUMN_ATTENDEE_NAME));
1584                         attendeeValues.put(Attendees.ATTENDEE_EMAIL,
1585                                 subCursor.getString(COLUMN_ATTENDEE_EMAIL));
1586                         attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
1587                                 subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
1588                         attendeeValues.put(Attendees.ATTENDEE_TYPE,
1589                                 subCursor.getInt(COLUMN_ATTENDEE_TYPE));
1590                         attendeeValues.put(Attendees.ATTENDEE_STATUS,
1591                                 subCursor.getInt(COLUMN_ATTENDEE_STATUS));
1592                         attendeeValues.put(Attendees.ATTENDEE_IDENTITY,
1593                                 subCursor.getString(COLUMN_ATTENDEE_IDENTITY));
1594                         attendeeValues.put(Attendees.ATTENDEE_ID_NAMESPACE,
1595                                 subCursor.getString(COLUMN_ATTENDEE_ID_NAMESPACE));
1596                         entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
1597                     }
1598                 } finally {
1599                     subCursor.close();
1600                 }
1601 
1602                 if (mResolver != null) {
1603                     subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
1604                             WHERE_EVENT_ID,
1605                             new String[] { Long.toString(eventId) } /* selectionArgs */,
1606                             null /* sortOrder */);
1607                 } else {
1608                     subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
1609                             WHERE_EVENT_ID,
1610                             new String[] { Long.toString(eventId) } /* selectionArgs */,
1611                             null /* sortOrder */);
1612                 }
1613                 try {
1614                     while (subCursor.moveToNext()) {
1615                         ContentValues extendedValues = new ContentValues();
1616                         extendedValues.put(ExtendedProperties._ID,
1617                                 subCursor.getString(COLUMN_ID));
1618                         extendedValues.put(ExtendedProperties.NAME,
1619                                 subCursor.getString(COLUMN_NAME));
1620                         extendedValues.put(ExtendedProperties.VALUE,
1621                                 subCursor.getString(COLUMN_VALUE));
1622                         entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
1623                     }
1624                 } finally {
1625                     subCursor.close();
1626                 }
1627 
1628                 cursor.moveToNext();
1629                 return entity;
1630             }
1631         }
1632     }
1633 
1634     /**
1635      * Constants and helpers for the Events table, which contains details for
1636      * individual events. <h3>Operations</h3> All operations can be done either
1637      * as an app or as a sync adapter. To perform an operation as a sync adapter
1638      * {@link #CALLER_IS_SYNCADAPTER} should be set to true and
1639      * {@link #ACCOUNT_NAME} and {@link #ACCOUNT_TYPE} must be set in the Uri
1640      * parameters. See
1641      * {@link Uri.Builder#appendQueryParameter(java.lang.String, java.lang.String)}
1642      * for details on adding parameters. Sync adapters have write access to more
1643      * columns but are restricted to a single account at a time.
1644      * <dl>
1645      * <dt><b>Insert</b></dt>
1646      * <dd>When inserting a new event the following fields must be included:
1647      * <ul>
1648      * <li>dtstart</li>
1649      * <li>dtend if the event is non-recurring</li>
1650      * <li>duration if the event is recurring</li>
1651      * <li>rrule or rdate if the event is recurring</li>
1652      * <li>eventTimezone</li>
1653      * <li>a calendar_id</li>
1654      * </ul>
1655      * There are also further requirements when inserting or updating an event.
1656      * See the section on Writing to Events.</dd>
1657      * <dt><b>Update</b></dt>
1658      * <dd>To perform an update of an Event the {@link Events#_ID} of the event
1659      * should be provided either as an appended id to the Uri (
1660      * {@link ContentUris#withAppendedId}) or as the first selection item--the
1661      * selection should start with "_id=?" and the first selectionArg should be
1662      * the _id of the event. Updates may also be done using a selection and no
1663      * id. Updating an event must respect the same rules as inserting and is
1664      * further restricted in the fields that can be written. See the section on
1665      * Writing to Events.</dd>
1666      * <dt><b>Delete</b></dt>
1667      * <dd>Events can be deleted either by the {@link Events#_ID} as an appended
1668      * id on the Uri or using any standard selection. If an appended id is used
1669      * a selection is not allowed. There are two versions of delete: as an app
1670      * and as a sync adapter. An app delete will set the deleted column on an
1671      * event and remove all instances of that event. A sync adapter delete will
1672      * remove the event from the database and all associated data.</dd>
1673      * <dt><b>Query</b></dt>
1674      * <dd>Querying the Events table will get you all information about a set of
1675      * events except their reminders, attendees, and extended properties. There
1676      * will be one row returned for each event that matches the query selection,
1677      * or at most a single row if the {@link Events#_ID} is appended to the Uri.
1678      * Recurring events will only return a single row regardless of the number
1679      * of times that event repeats.</dd>
1680      * </dl>
1681      * <h3>Writing to Events</h3> There are further restrictions on all Updates
1682      * and Inserts in the Events table:
1683      * <ul>
1684      * <li>If allDay is set to 1 eventTimezone must be "UTC"
1685      * and the time must correspond to a midnight boundary.</li>
1686      * <li>Exceptions are not allowed to recur. If rrule or rdate is not empty,
1687      * original_id and original_sync_id must be empty.</li>
1688      * <li>In general a calendar_id should not be modified after insertion. This
1689      * is not explicitly forbidden but many sync adapters will not behave in an
1690      * expected way if the calendar_id is modified.</li>
1691      * </ul>
1692      * The following Events columns are writable by both an app and a sync
1693      * adapter.
1694      * <ul>
1695      * <li>{@link #CALENDAR_ID}</li>
1696      * <li>{@link #ORGANIZER}</li>
1697      * <li>{@link #TITLE}</li>
1698      * <li>{@link #EVENT_LOCATION}</li>
1699      * <li>{@link #DESCRIPTION}</li>
1700      * <li>{@link #EVENT_COLOR}</li>
1701      * <li>{@link #DTSTART}</li>
1702      * <li>{@link #DTEND}</li>
1703      * <li>{@link #EVENT_TIMEZONE}</li>
1704      * <li>{@link #EVENT_END_TIMEZONE}</li>
1705      * <li>{@link #DURATION}</li>
1706      * <li>{@link #ALL_DAY}</li>
1707      * <li>{@link #RRULE}</li>
1708      * <li>{@link #RDATE}</li>
1709      * <li>{@link #EXRULE}</li>
1710      * <li>{@link #EXDATE}</li>
1711      * <li>{@link #ORIGINAL_ID}</li>
1712      * <li>{@link #ORIGINAL_SYNC_ID}</li>
1713      * <li>{@link #ORIGINAL_INSTANCE_TIME}</li>
1714      * <li>{@link #ORIGINAL_ALL_DAY}</li>
1715      * <li>{@link #ACCESS_LEVEL}</li>
1716      * <li>{@link #AVAILABILITY}</li>
1717      * <li>{@link #GUESTS_CAN_MODIFY}</li>
1718      * <li>{@link #GUESTS_CAN_INVITE_OTHERS}</li>
1719      * <li>{@link #GUESTS_CAN_SEE_GUESTS}</li>
1720      * <li>{@link #CUSTOM_APP_PACKAGE}</li>
1721      * <li>{@link #CUSTOM_APP_URI}</li>
1722      * <li>{@link #UID_2445}</li>
1723      * </ul>
1724      * The following Events columns are writable only by a sync adapter
1725      * <ul>
1726      * <li>{@link #DIRTY}</li>
1727      * <li>{@link #MUTATORS}</li>
1728      * <li>{@link #_SYNC_ID}</li>
1729      * <li>{@link #SYNC_DATA1}</li>
1730      * <li>{@link #SYNC_DATA2}</li>
1731      * <li>{@link #SYNC_DATA3}</li>
1732      * <li>{@link #SYNC_DATA4}</li>
1733      * <li>{@link #SYNC_DATA5}</li>
1734      * <li>{@link #SYNC_DATA6}</li>
1735      * <li>{@link #SYNC_DATA7}</li>
1736      * <li>{@link #SYNC_DATA8}</li>
1737      * <li>{@link #SYNC_DATA9}</li>
1738      * <li>{@link #SYNC_DATA10}</li>
1739      * </ul>
1740      * The remaining columns are either updated by the provider only or are
1741      * views into other tables and cannot be changed through the Events table.
1742      */
1743     public static final class Events implements BaseColumns, SyncColumns, EventsColumns,
1744             CalendarColumns {
1745 
1746         /**
1747          * The content:// style URL for interacting with events. Appending an
1748          * event id using {@link ContentUris#withAppendedId(Uri, long)} will
1749          * specify a single event.
1750          */
1751         @SuppressWarnings("hiding")
1752         public static final Uri CONTENT_URI =
1753                 Uri.parse("content://" + AUTHORITY + "/events");
1754 
1755         /**
1756          * The content:// style URL for querying Events table in the managed profile. Appending an
1757          * event id using {@link ContentUris#withAppendedId(Uri, long)} specifies a single event.
1758          *
1759          * <p>The following columns are allowed to be queried via this uri:
1760          * <ul>
1761          * <li>{@link #_ID}</li>
1762          * <li>{@link #CALENDAR_ID}</li>
1763          * <li>{@link #TITLE}</li>
1764          * <li>{@link #EVENT_LOCATION}</li>
1765          * <li>{@link #EVENT_COLOR}</li>
1766          * <li>{@link #STATUS}</li>
1767          * <li>{@link #DTSTART}</li>
1768          * <li>{@link #DTEND}</li>
1769          * <li>{@link #EVENT_TIMEZONE}</li>
1770          * <li>{@link #EVENT_END_TIMEZONE}</li>
1771          * <li>{@link #DURATION}</li>
1772          * <li>{@link #ALL_DAY}</li>
1773          * <li>{@link #AVAILABILITY}</li>
1774          * <li>{@link #RRULE}</li>
1775          * <li>{@link #RDATE}</li>
1776          * <li>{@link #LAST_DATE}</li>
1777          * <li>{@link #EXRULE}</li>
1778          * <li>{@link #EXDATE}</li>
1779          * <li>{@link #SELF_ATTENDEE_STATUS}</li>
1780          * <li>{@link #DISPLAY_COLOR}</li>
1781          * <li>{@link #CALENDAR_COLOR}</li>
1782          * <li>{@link #VISIBLE}</li>
1783          * <li>{@link #CALENDAR_TIME_ZONE}</li>
1784          * <li>{@link #IS_PRIMARY}</li>
1785          * </ul>
1786          *
1787          * <p>{@link IllegalArgumentException} is thrown if there exists columns in the
1788          * projection of the query to this uri that are not contained in the above list.
1789          *
1790          * <p>This uri returns an empty cursor if the calling user is not a parent profile
1791          * of a managed profile, or the managed profile is disabled, or cross-profile calendar is
1792          * disabled in Settings, or this uri is queried from a package that is not allowed by
1793          * the profile owner of the managed profile via
1794          * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
1795          *
1796          * <p>Apps can register a {@link android.database.ContentObserver} for this URI to listen
1797          * to changes.
1798          *
1799          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
1800          */
1801         @NonNull
1802         public static final Uri ENTERPRISE_CONTENT_URI =
1803                 Uri.parse("content://" + AUTHORITY + "/enterprise/events");
1804 
1805         /**
1806          * The content:// style URI for recurring event exceptions.  Insertions require an
1807          * appended event ID.  Deletion of exceptions requires both the original event ID and
1808          * the exception event ID (see {@link Uri.Builder#appendPath}).
1809          */
1810         public static final Uri CONTENT_EXCEPTION_URI =
1811                 Uri.parse("content://" + AUTHORITY + "/exception");
1812 
1813         /**
1814          * This utility class cannot be instantiated
1815          */
Events()1816         private Events() {}
1817 
1818         /**
1819          * The default sort order for this table
1820          */
1821         private static final String DEFAULT_SORT_ORDER = "";
1822 
1823         /**
1824          * These are columns that should only ever be updated by the provider,
1825          * either because they are views mapped to another table or because they
1826          * are used for provider only functionality. TODO move to provider
1827          *
1828          * @hide
1829          */
1830         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1831         public static String[] PROVIDER_WRITABLE_COLUMNS = new String[] {
1832                 ACCOUNT_NAME,
1833                 ACCOUNT_TYPE,
1834                 CAL_SYNC1,
1835                 CAL_SYNC2,
1836                 CAL_SYNC3,
1837                 CAL_SYNC4,
1838                 CAL_SYNC5,
1839                 CAL_SYNC6,
1840                 CAL_SYNC7,
1841                 CAL_SYNC8,
1842                 CAL_SYNC9,
1843                 CAL_SYNC10,
1844                 ALLOWED_REMINDERS,
1845                 ALLOWED_ATTENDEE_TYPES,
1846                 ALLOWED_AVAILABILITY,
1847                 CALENDAR_ACCESS_LEVEL,
1848                 CALENDAR_COLOR,
1849                 CALENDAR_TIME_ZONE,
1850                 CAN_MODIFY_TIME_ZONE,
1851                 CAN_ORGANIZER_RESPOND,
1852                 CALENDAR_DISPLAY_NAME,
1853                 CAN_PARTIALLY_UPDATE,
1854                 SYNC_EVENTS,
1855                 VISIBLE,
1856         };
1857 
1858         /**
1859          * These fields are only writable by a sync adapter. To modify them the
1860          * caller must include CALLER_IS_SYNCADAPTER, _SYNC_ACCOUNT, and
1861          * _SYNC_ACCOUNT_TYPE in the query parameters. TODO move to provider.
1862          *
1863          * @hide
1864          */
1865         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1866         @TestApi
1867         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
1868             _SYNC_ID,
1869             DIRTY,
1870             MUTATORS,
1871             SYNC_DATA1,
1872             SYNC_DATA2,
1873             SYNC_DATA3,
1874             SYNC_DATA4,
1875             SYNC_DATA5,
1876             SYNC_DATA6,
1877             SYNC_DATA7,
1878             SYNC_DATA8,
1879             SYNC_DATA9,
1880             SYNC_DATA10,
1881         };
1882     }
1883 
1884     /**
1885      * Fields and helpers for interacting with Instances. An instance is a
1886      * single occurrence of an event including time zone specific start and end
1887      * days and minutes. The instances table is not writable and only provides a
1888      * way to query event occurrences.
1889      */
1890     public static final class Instances implements BaseColumns, EventsColumns, CalendarColumns {
1891 
1892         private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?";
1893         private static final String[] WHERE_CALENDARS_ARGS = {
1894             "1"
1895         };
1896 
1897         /**
1898          * This utility class cannot be instantiated
1899          */
Instances()1900         private Instances() {}
1901 
1902         /**
1903          * Performs a query to return all visible instances in the given range.
1904          * This is a blocking function and should not be done on the UI thread.
1905          * This will cause an expansion of recurring events to fill this time
1906          * range if they are not already expanded and will slow down for larger
1907          * time ranges with many recurring events.
1908          *
1909          * @param cr The ContentResolver to use for the query
1910          * @param projection The columns to return
1911          * @param begin The start of the time range to query in UTC millis since
1912          *            epoch
1913          * @param end The end of the time range to query in UTC millis since
1914          *            epoch
1915          * @return A Cursor containing all instances in the given range
1916          */
query(ContentResolver cr, String[] projection, long begin, long end)1917         public static final Cursor query(ContentResolver cr, String[] projection,
1918                                          long begin, long end) {
1919             Uri.Builder builder = CONTENT_URI.buildUpon();
1920             ContentUris.appendId(builder, begin);
1921             ContentUris.appendId(builder, end);
1922             return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
1923                     WHERE_CALENDARS_ARGS, DEFAULT_SORT_ORDER);
1924         }
1925 
1926         /**
1927          * Performs a query to return all visible instances in the given range
1928          * that match the given query. This is a blocking function and should
1929          * not be done on the UI thread. This will cause an expansion of
1930          * recurring events to fill this time range if they are not already
1931          * expanded and will slow down for larger time ranges with many
1932          * recurring events.
1933          *
1934          * @param cr The ContentResolver to use for the query
1935          * @param projection The columns to return
1936          * @param begin The start of the time range to query in UTC millis since
1937          *            epoch
1938          * @param end The end of the time range to query in UTC millis since
1939          *            epoch
1940          * @param searchQuery A string of space separated search terms. Segments
1941          *            enclosed by double quotes will be treated as a single
1942          *            term.
1943          * @return A Cursor of instances matching the search terms in the given
1944          *         time range
1945          */
query(ContentResolver cr, String[] projection, long begin, long end, String searchQuery)1946         public static final Cursor query(ContentResolver cr, String[] projection,
1947                                          long begin, long end, String searchQuery) {
1948             Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
1949             ContentUris.appendId(builder, begin);
1950             ContentUris.appendId(builder, end);
1951             builder = builder.appendPath(searchQuery);
1952             return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
1953                     WHERE_CALENDARS_ARGS, DEFAULT_SORT_ORDER);
1954         }
1955 
1956         /**
1957          * The content:// style URL for querying an instance range. The begin
1958          * and end of the range to query should be added as path segments if
1959          * this is used directly.
1960          */
1961         @SuppressWarnings("hiding")
1962         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
1963                 "/instances/when");
1964         /**
1965          * The content:// style URL for querying an instance range by Julian
1966          * Day. The start and end day should be added as path segments if this
1967          * is used directly.
1968          */
1969         public static final Uri CONTENT_BY_DAY_URI =
1970             Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
1971         /**
1972          * The content:// style URL for querying an instance range with a search
1973          * term. The begin, end, and search string should be appended as path
1974          * segments if this is used directly.
1975          */
1976         public static final Uri CONTENT_SEARCH_URI = Uri.parse("content://" + AUTHORITY +
1977                 "/instances/search");
1978         /**
1979          * The content:// style URL for querying an instance range with a search
1980          * term. The start day, end day, and search string should be appended as
1981          * path segments if this is used directly.
1982          */
1983         public static final Uri CONTENT_SEARCH_BY_DAY_URI =
1984             Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
1985 
1986         /**
1987          * The content:// style URL for querying an instance range in the managed profile.
1988          * It supports similar semantics as {@link #CONTENT_URI}.
1989          *
1990          * <p>The following columns plus the columns that are allowed by
1991          * {@link Events#ENTERPRISE_CONTENT_URI} are allowed to be queried via this uri:
1992          * <ul>
1993          * <li>{@link #_ID}</li>
1994          * <li>{@link #EVENT_ID}</li>
1995          * <li>{@link #BEGIN}</li>
1996          * <li>{@link #END}</li>
1997          * <li>{@link #START_DAY}</li>
1998          * <li>{@link #END_DAY}</li>
1999          * <li>{@link #START_MINUTE}</li>
2000          * <li>{@link #END_MINUTE}</li>
2001          * </ul>
2002          *
2003          * <p>{@link IllegalArgumentException} is thrown if there exists columns in the
2004          * projection of the query to this uri that are not contained in the above list.
2005          *
2006          * <p>This uri returns an empty cursor if the calling user is not a parent profile
2007          * of a managed profile, or the managed profile is disabled, or cross-profile calendar is
2008          * disabled in Settings, or this uri is queried from a package that is not allowed by
2009          * the profile owner of the managed profile via
2010          * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
2011          *
2012          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
2013          */
2014         @NonNull
2015         public static final Uri ENTERPRISE_CONTENT_URI =
2016                 Uri.parse("content://" + AUTHORITY + "/enterprise/instances/when");
2017 
2018         /**
2019          * The content:// style URL for querying an instance range by Julian
2020          * Day in the managed profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI}
2021          * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
2022          */
2023         @NonNull
2024         public static final Uri ENTERPRISE_CONTENT_BY_DAY_URI =
2025                 Uri.parse("content://" + AUTHORITY + "/enterprise/instances/whenbyday");
2026 
2027         /**
2028          * The content:// style URL for querying an instance range with a search
2029          * term in the managed profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI}
2030          * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
2031          */
2032         @NonNull
2033         public static final Uri ENTERPRISE_CONTENT_SEARCH_URI =
2034                 Uri.parse("content://" + AUTHORITY + "/enterprise/instances/search");
2035 
2036         /**
2037          * The content:// style URL for querying an instance range with a search
2038          * term in the managed profile. It supports similar semantics as
2039          * {@link #CONTENT_SEARCH_BY_DAY_URI} and performs similar checks as
2040          * {@link #ENTERPRISE_CONTENT_URI}.
2041          */
2042         @NonNull
2043         public static final Uri ENTERPRISE_CONTENT_SEARCH_BY_DAY_URI =
2044                 Uri.parse("content://" + AUTHORITY + "/enterprise/instances/searchbyday");
2045 
2046         /**
2047          * The default sort order for this table.
2048          */
2049         private static final String DEFAULT_SORT_ORDER = "begin ASC";
2050 
2051         /**
2052          * The beginning time of the instance, in UTC milliseconds. Column name.
2053          * <P>Type: INTEGER (long; millis since epoch)</P>
2054          */
2055         public static final String BEGIN = "begin";
2056 
2057         /**
2058          * The ending time of the instance, in UTC milliseconds. Column name.
2059          * <P>Type: INTEGER (long; millis since epoch)</P>
2060          */
2061         public static final String END = "end";
2062 
2063         /**
2064          * The _id of the event for this instance. Column name.
2065          * <P>Type: INTEGER (long, foreign key to the Events table)</P>
2066          */
2067         public static final String EVENT_ID = "event_id";
2068 
2069         /**
2070          * The Julian start day of the instance, relative to the local time
2071          * zone. Column name.
2072          * <P>Type: INTEGER (int)</P>
2073          */
2074         public static final String START_DAY = "startDay";
2075 
2076         /**
2077          * The Julian end day of the instance, relative to the local time
2078          * zone. Column name.
2079          * <P>Type: INTEGER (int)</P>
2080          */
2081         public static final String END_DAY = "endDay";
2082 
2083         /**
2084          * The start minute of the instance measured from midnight in the
2085          * local time zone. Column name.
2086          * <P>Type: INTEGER (int)</P>
2087          */
2088         public static final String START_MINUTE = "startMinute";
2089 
2090         /**
2091          * The end minute of the instance measured from midnight in the
2092          * local time zone. Column name.
2093          * <P>Type: INTEGER (int)</P>
2094          */
2095         public static final String END_MINUTE = "endMinute";
2096     }
2097 
2098     protected interface CalendarCacheColumns {
2099         /**
2100          * The key for the setting. Keys are defined in {@link CalendarCache}.
2101          */
2102         public static final String KEY = "key";
2103 
2104         /**
2105          * The value of the given setting.
2106          */
2107         public static final String VALUE = "value";
2108     }
2109 
2110     /**
2111      * CalendarCache stores some settings for calendar including the current
2112      * time zone for the instances. These settings are stored using a key/value
2113      * scheme. A {@link #KEY} must be specified when updating these values.
2114      */
2115     public static final class CalendarCache implements CalendarCacheColumns {
2116         /**
2117          * The URI to use for retrieving the properties from the Calendar db.
2118          */
2119         public static final Uri URI =
2120                 Uri.parse("content://" + AUTHORITY + "/properties");
2121 
2122         /**
2123          * This utility class cannot be instantiated
2124          */
CalendarCache()2125         private CalendarCache() {}
2126 
2127         /**
2128          * They key for updating the use of auto/home time zones in Calendar.
2129          * Valid values are {@link #TIMEZONE_TYPE_AUTO} or
2130          * {@link #TIMEZONE_TYPE_HOME}.
2131          */
2132         public static final String KEY_TIMEZONE_TYPE = "timezoneType";
2133 
2134         /**
2135          * The key for updating the time zone used by the provider when it
2136          * generates the instances table. This should only be written if the
2137          * type is set to {@link #TIMEZONE_TYPE_HOME}. A valid time zone id
2138          * should be written to this field.
2139          */
2140         public static final String KEY_TIMEZONE_INSTANCES = "timezoneInstances";
2141 
2142         /**
2143          * The key for reading the last time zone set by the user. This should
2144          * only be read by apps and it will be automatically updated whenever
2145          * {@link #KEY_TIMEZONE_INSTANCES} is updated with
2146          * {@link #TIMEZONE_TYPE_HOME} set.
2147          */
2148         public static final String KEY_TIMEZONE_INSTANCES_PREVIOUS = "timezoneInstancesPrevious";
2149 
2150         /**
2151          * The value to write to {@link #KEY_TIMEZONE_TYPE} if the provider
2152          * should stay in sync with the device's time zone.
2153          */
2154         public static final String TIMEZONE_TYPE_AUTO = "auto";
2155 
2156         /**
2157          * The value to write to {@link #KEY_TIMEZONE_TYPE} if the provider
2158          * should use a fixed time zone set by the user.
2159          */
2160         public static final String TIMEZONE_TYPE_HOME = "home";
2161     }
2162 
2163     /**
2164      * A few Calendar globals are needed in the CalendarProvider for expanding
2165      * the Instances table and these are all stored in the first (and only) row
2166      * of the CalendarMetaData table.
2167      *
2168      * @hide
2169      */
2170     protected interface CalendarMetaDataColumns {
2171         /**
2172          * The local timezone that was used for precomputing the fields
2173          * in the Instances table.
2174          */
2175         public static final String LOCAL_TIMEZONE = "localTimezone";
2176 
2177         /**
2178          * The minimum time used in expanding the Instances table,
2179          * in UTC milliseconds.
2180          * <P>Type: INTEGER</P>
2181          */
2182         public static final String MIN_INSTANCE = "minInstance";
2183 
2184         /**
2185          * The maximum time used in expanding the Instances table,
2186          * in UTC milliseconds.
2187          * <P>Type: INTEGER</P>
2188          */
2189         public static final String MAX_INSTANCE = "maxInstance";
2190 
2191         /**
2192          * The minimum Julian day in the EventDays table.
2193          * <P>Type: INTEGER</P>
2194          */
2195         public static final String MIN_EVENTDAYS = "minEventDays";
2196 
2197         /**
2198          * The maximum Julian day in the EventDays table.
2199          * <P>Type: INTEGER</P>
2200          */
2201         public static final String MAX_EVENTDAYS = "maxEventDays";
2202     }
2203 
2204     /**
2205      * @hide
2206      */
2207     public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns {
2208 
2209         /**
2210          * This utility class cannot be instantiated
2211          */
CalendarMetaData()2212         private CalendarMetaData() {}
2213     }
2214 
2215     protected interface EventDaysColumns {
2216         /**
2217          * The Julian starting day number. Column name.
2218          * <P>Type: INTEGER (int)</P>
2219          */
2220         public static final String STARTDAY = "startDay";
2221         /**
2222          * The Julian ending day number. Column name.
2223          * <P>Type: INTEGER (int)</P>
2224          */
2225         public static final String ENDDAY = "endDay";
2226 
2227     }
2228 
2229     /**
2230      * Fields and helpers for querying for a list of days that contain events.
2231      */
2232     public static final class EventDays implements EventDaysColumns {
2233         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
2234                 + "/instances/groupbyday");
2235         private static final String SELECTION = "selected=1";
2236 
2237         /**
2238          * This utility class cannot be instantiated
2239          */
EventDays()2240         private EventDays() {}
2241 
2242         /**
2243          * Retrieves the days with events for the Julian days starting at
2244          * "startDay" for "numDays". It returns a cursor containing startday and
2245          * endday representing the max range of days for all events beginning on
2246          * each startday.This is a blocking function and should not be done on
2247          * the UI thread.
2248          *
2249          * @param cr the ContentResolver
2250          * @param startDay the first Julian day in the range
2251          * @param numDays the number of days to load (must be at least 1)
2252          * @param projection the columns to return in the cursor
2253          * @return a database cursor containing a list of start and end days for
2254          *         events
2255          */
query(ContentResolver cr, int startDay, int numDays, String[] projection)2256         public static final Cursor query(ContentResolver cr, int startDay, int numDays,
2257                 String[] projection) {
2258             if (numDays < 1) {
2259                 return null;
2260             }
2261             int endDay = startDay + numDays - 1;
2262             Uri.Builder builder = CONTENT_URI.buildUpon();
2263             ContentUris.appendId(builder, startDay);
2264             ContentUris.appendId(builder, endDay);
2265             return cr.query(builder.build(), projection, SELECTION,
2266                     null /* selection args */, STARTDAY);
2267         }
2268     }
2269 
2270     protected interface RemindersColumns {
2271         /**
2272          * The event the reminder belongs to. Column name.
2273          * <P>Type: INTEGER (foreign key to the Events table)</P>
2274          */
2275         public static final String EVENT_ID = "event_id";
2276 
2277         /**
2278          * The minutes prior to the event that the alarm should ring.  -1
2279          * specifies that we should use the default value for the system.
2280          * Column name.
2281          * <P>Type: INTEGER</P>
2282          */
2283         public static final String MINUTES = "minutes";
2284 
2285         /**
2286          * Passing this as a minutes value will use the default reminder
2287          * minutes.
2288          */
2289         public static final int MINUTES_DEFAULT = -1;
2290 
2291         /**
2292          * The alarm method, as set on the server. {@link #METHOD_DEFAULT},
2293          * {@link #METHOD_ALERT}, {@link #METHOD_EMAIL}, {@link #METHOD_SMS} and
2294          * {@link #METHOD_ALARM} are possible values; the device will only
2295          * process {@link #METHOD_DEFAULT} and {@link #METHOD_ALERT} reminders
2296          * (the other types are simply stored so we can send the same reminder
2297          * info back to the server when we make changes).
2298          */
2299         public static final String METHOD = "method";
2300 
2301         public static final int METHOD_DEFAULT = 0;
2302         public static final int METHOD_ALERT = 1;
2303         public static final int METHOD_EMAIL = 2;
2304         public static final int METHOD_SMS = 3;
2305         public static final int METHOD_ALARM = 4;
2306     }
2307 
2308     /**
2309      * Fields and helpers for accessing reminders for an event. Each row of this
2310      * table represents a single reminder for an event. Calling
2311      * {@link #query(ContentResolver, long, String[])} will return a list of reminders for
2312      * the event with the given eventId. Both apps and sync adapters may write
2313      * to this table. There are three writable fields and all of them must be
2314      * included when inserting a new reminder. They are:
2315      * <ul>
2316      * <li>{@link #EVENT_ID}</li>
2317      * <li>{@link #MINUTES}</li>
2318      * <li>{@link #METHOD}</li>
2319      * </ul>
2320      */
2321     public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
2322         private static final String REMINDERS_WHERE = CalendarContract.Reminders.EVENT_ID + "=?";
2323         @SuppressWarnings("hiding")
2324         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
2325 
2326         /**
2327          * This utility class cannot be instantiated
2328          */
Reminders()2329         private Reminders() {}
2330 
2331         /**
2332          * Queries all reminders associated with the given event. This is a
2333          * blocking call and should not be done on the UI thread.
2334          *
2335          * @param cr The content resolver to use for the query
2336          * @param eventId The id of the event to retrieve reminders for
2337          * @param projection the columns to return in the cursor
2338          * @return A Cursor containing all reminders for the event
2339          */
query(ContentResolver cr, long eventId, String[] projection)2340         public static final Cursor query(ContentResolver cr, long eventId, String[] projection) {
2341             String[] remArgs = {Long.toString(eventId)};
2342             return cr.query(CONTENT_URI, projection, REMINDERS_WHERE, remArgs /*selection args*/,
2343                     null /* sort order */);
2344         }
2345     }
2346 
2347     protected interface CalendarAlertsColumns {
2348         /**
2349          * The event that the alert belongs to. Column name.
2350          * <P>Type: INTEGER (foreign key to the Events table)</P>
2351          */
2352         public static final String EVENT_ID = "event_id";
2353 
2354         /**
2355          * The start time of the event, in UTC. Column name.
2356          * <P>Type: INTEGER (long; millis since epoch)</P>
2357          */
2358         public static final String BEGIN = "begin";
2359 
2360         /**
2361          * The end time of the event, in UTC. Column name.
2362          * <P>Type: INTEGER (long; millis since epoch)</P>
2363          */
2364         public static final String END = "end";
2365 
2366         /**
2367          * The alarm time of the event, in UTC. Column name.
2368          * <P>Type: INTEGER (long; millis since epoch)</P>
2369          */
2370         public static final String ALARM_TIME = "alarmTime";
2371 
2372         /**
2373          * The creation time of this database entry, in UTC.
2374          * Useful for debugging missed reminders. Column name.
2375          * <P>Type: INTEGER (long; millis since epoch)</P>
2376          */
2377         public static final String CREATION_TIME = "creationTime";
2378 
2379         /**
2380          * The time that the alarm broadcast was received by the Calendar app,
2381          * in UTC. Useful for debugging missed reminders. Column name.
2382          * <P>Type: INTEGER (long; millis since epoch)</P>
2383          */
2384         public static final String RECEIVED_TIME = "receivedTime";
2385 
2386         /**
2387          * The time that the notification was created by the Calendar app,
2388          * in UTC. Useful for debugging missed reminders. Column name.
2389          * <P>Type: INTEGER (long; millis since epoch)</P>
2390          */
2391         public static final String NOTIFY_TIME = "notifyTime";
2392 
2393         /**
2394          * The state of this alert. It starts out as {@link #STATE_SCHEDULED}, then
2395          * when the alarm goes off, it changes to {@link #STATE_FIRED}, and then when
2396          * the user dismisses the alarm it changes to {@link #STATE_DISMISSED}. Column
2397          * name.
2398          * <P>Type: INTEGER</P>
2399          */
2400         public static final String STATE = "state";
2401 
2402         /**
2403          * An alert begins in this state when it is first created.
2404          */
2405         public static final int STATE_SCHEDULED = 0;
2406         /**
2407          * After a notification for an alert has been created it should be
2408          * updated to fired.
2409          */
2410         public static final int STATE_FIRED = 1;
2411         /**
2412          * Once the user has dismissed the notification the alert's state should
2413          * be set to dismissed so it is not fired again.
2414          */
2415         public static final int STATE_DISMISSED = 2;
2416 
2417         /**
2418          * The number of minutes that this alarm precedes the start time. Column
2419          * name.
2420          * <P>Type: INTEGER</P>
2421          */
2422         public static final String MINUTES = "minutes";
2423 
2424         /**
2425          * The default sort order for this alerts queries
2426          */
2427         public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
2428     }
2429 
2430     /**
2431      * Fields and helpers for accessing calendar alerts information. These
2432      * fields are for tracking which alerts have been fired. Scheduled alarms
2433      * will generate an intent using {@link #ACTION_EVENT_REMINDER}. Apps that
2434      * receive this action may update the {@link #STATE} for the reminder when
2435      * they have finished handling it. Apps that have their notifications
2436      * disabled should not modify the table to ensure that they do not conflict
2437      * with another app that is generating a notification. In general, apps
2438      * should not need to write to this table directly except to update the
2439      * state of a reminder.
2440      */
2441     public static final class CalendarAlerts implements BaseColumns,
2442             CalendarAlertsColumns, EventsColumns, CalendarColumns {
2443 
2444         /**
2445          * @hide
2446          */
2447         public static final String TABLE_NAME = "CalendarAlerts";
2448         /**
2449          * The Uri for querying calendar alert information
2450          */
2451         @SuppressWarnings("hiding")
2452         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
2453                 "/calendar_alerts");
2454 
2455         /**
2456          * This utility class cannot be instantiated
2457          */
CalendarAlerts()2458         private CalendarAlerts() {}
2459 
2460         private static final String WHERE_ALARM_EXISTS = EVENT_ID + "=?"
2461                 + " AND " + BEGIN + "=?"
2462                 + " AND " + ALARM_TIME + "=?";
2463 
2464         private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?";
2465         private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC";
2466 
2467         private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + STATE_SCHEDULED
2468                 + " AND " + ALARM_TIME + "<?"
2469                 + " AND " + ALARM_TIME + ">?"
2470                 + " AND " + END + ">=?";
2471 
2472         /**
2473          * This URI is for grouping the query results by event_id and begin
2474          * time.  This will return one result per instance of an event.  So
2475          * events with multiple alarms will appear just once, but multiple
2476          * instances of a repeating event will show up multiple times.
2477          */
2478         public static final Uri CONTENT_URI_BY_INSTANCE =
2479             Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
2480 
2481         private static final boolean DEBUG = false;
2482 
2483         /**
2484          * Helper for inserting an alarm time associated with an event TODO move
2485          * to Provider
2486          *
2487          * @hide
2488          */
insert(ContentResolver cr, long eventId, long begin, long end, long alarmTime, int minutes)2489         public static final Uri insert(ContentResolver cr, long eventId,
2490                 long begin, long end, long alarmTime, int minutes) {
2491             ContentValues values = new ContentValues();
2492             values.put(CalendarAlerts.EVENT_ID, eventId);
2493             values.put(CalendarAlerts.BEGIN, begin);
2494             values.put(CalendarAlerts.END, end);
2495             values.put(CalendarAlerts.ALARM_TIME, alarmTime);
2496             long currentTime = System.currentTimeMillis();
2497             values.put(CalendarAlerts.CREATION_TIME, currentTime);
2498             values.put(CalendarAlerts.RECEIVED_TIME, 0);
2499             values.put(CalendarAlerts.NOTIFY_TIME, 0);
2500             values.put(CalendarAlerts.STATE, STATE_SCHEDULED);
2501             values.put(CalendarAlerts.MINUTES, minutes);
2502             return cr.insert(CONTENT_URI, values);
2503         }
2504 
2505         /**
2506          * Finds the next alarm after (or equal to) the given time and returns
2507          * the time of that alarm or -1 if no such alarm exists. This is a
2508          * blocking call and should not be done on the UI thread. TODO move to
2509          * provider
2510          *
2511          * @param cr the ContentResolver
2512          * @param millis the time in UTC milliseconds
2513          * @return the next alarm time greater than or equal to "millis", or -1
2514          *         if no such alarm exists.
2515          * @hide
2516          */
2517         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
findNextAlarmTime(ContentResolver cr, long millis)2518         public static final long findNextAlarmTime(ContentResolver cr, long millis) {
2519             String selection = ALARM_TIME + ">=" + millis;
2520             // TODO: construct an explicit SQL query so that we can add
2521             // "LIMIT 1" to the end and get just one result.
2522             String[] projection = new String[] { ALARM_TIME };
2523             Cursor cursor = cr.query(CONTENT_URI, projection, WHERE_FINDNEXTALARMTIME,
2524                     (new String[] {
2525                         Long.toString(millis)
2526                     }), SORT_ORDER_ALARMTIME_ASC);
2527             long alarmTime = -1;
2528             try {
2529                 if (cursor != null && cursor.moveToFirst()) {
2530                     alarmTime = cursor.getLong(0);
2531                 }
2532             } finally {
2533                 if (cursor != null) {
2534                     cursor.close();
2535                 }
2536             }
2537             return alarmTime;
2538         }
2539 
2540         /**
2541          * Searches the CalendarAlerts table for alarms that should have fired
2542          * but have not and then reschedules them. This method can be called at
2543          * boot time to restore alarms that may have been lost due to a phone
2544          * reboot. TODO move to provider
2545          *
2546          * @param cr the ContentResolver
2547          * @param context the Context
2548          * @param manager the AlarmManager
2549          * @hide
2550          */
2551         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
rescheduleMissedAlarms(ContentResolver cr, Context context, AlarmManager manager)2552         public static final void rescheduleMissedAlarms(ContentResolver cr,
2553                 Context context, AlarmManager manager) {
2554             // Get all the alerts that have been scheduled but have not fired
2555             // and should have fired by now and are not too old.
2556             long now = System.currentTimeMillis();
2557             long ancient = now - DateUtils.DAY_IN_MILLIS;
2558             String[] projection = new String[] {
2559                     ALARM_TIME,
2560             };
2561 
2562             // TODO: construct an explicit SQL query so that we can add
2563             // "GROUPBY" instead of doing a sort and de-dup
2564             Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
2565                     WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
2566                             Long.toString(now), Long.toString(ancient), Long.toString(now)
2567                     }), SORT_ORDER_ALARMTIME_ASC);
2568             if (cursor == null) {
2569                 return;
2570             }
2571 
2572             if (DEBUG) {
2573                 Log.d(TAG, "missed alarms found: " + cursor.getCount());
2574             }
2575 
2576             try {
2577                 long alarmTime = -1;
2578 
2579                 while (cursor.moveToNext()) {
2580                     long newAlarmTime = cursor.getLong(0);
2581                     if (alarmTime != newAlarmTime) {
2582                         if (DEBUG) {
2583                             Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
2584                         }
2585                         scheduleAlarm(context, manager, newAlarmTime);
2586                         alarmTime = newAlarmTime;
2587                     }
2588                 }
2589             } finally {
2590                 cursor.close();
2591             }
2592         }
2593 
2594         /**
2595          * Schedules an alarm intent with the system AlarmManager that will
2596          * notify listeners when a reminder should be fired. The provider will
2597          * keep scheduled reminders up to date but apps may use this to
2598          * implement snooze functionality without modifying the reminders table.
2599          * Scheduled alarms will generate an intent using
2600          * {@link #ACTION_EVENT_REMINDER}. TODO Move to provider
2601          *
2602          * @param context A context for referencing system resources
2603          * @param manager The AlarmManager to use or null
2604          * @param alarmTime The time to fire the intent in UTC millis since
2605          *            epoch
2606          * @hide
2607          */
2608         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
scheduleAlarm(Context context, AlarmManager manager, long alarmTime)2609         public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
2610             if (DEBUG) {
2611                 String schedTime = TimeMigrationUtils.formatMillisWithFixedFormat(alarmTime);
2612                 Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
2613             }
2614 
2615             if (manager == null) {
2616                 manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
2617             }
2618 
2619             Intent intent = new Intent(ACTION_EVENT_REMINDER);
2620             intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
2621             intent.putExtra(ALARM_TIME, alarmTime);
2622             intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
2623 
2624             // Disable strict mode VM policy violations temporarily for intents that contain a
2625             // content URI but don't have FLAG_GRANT_READ_URI_PERMISSION.
2626             StrictMode.VmPolicy oldVmPolicy = StrictMode.allowVmViolations();
2627             PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
2628                     PendingIntent.FLAG_IMMUTABLE);
2629             StrictMode.setVmPolicy(oldVmPolicy);
2630 
2631             manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pi);
2632         }
2633 
2634         /**
2635          * Searches for an entry in the CalendarAlerts table that matches the
2636          * given event id, begin time and alarm time. If one is found then this
2637          * alarm already exists and this method returns true. TODO Move to
2638          * provider
2639          *
2640          * @param cr the ContentResolver
2641          * @param eventId the event id to match
2642          * @param begin the start time of the event in UTC millis
2643          * @param alarmTime the alarm time of the event in UTC millis
2644          * @return true if there is already an alarm for the given event with
2645          *         the same start time and alarm time.
2646          * @hide
2647          */
alarmExists(ContentResolver cr, long eventId, long begin, long alarmTime)2648         public static final boolean alarmExists(ContentResolver cr, long eventId,
2649                 long begin, long alarmTime) {
2650             // TODO: construct an explicit SQL query so that we can add
2651             // "LIMIT 1" to the end and get just one result.
2652             String[] projection = new String[] { ALARM_TIME };
2653             Cursor cursor = cr.query(CONTENT_URI, projection, WHERE_ALARM_EXISTS,
2654                     (new String[] {
2655                             Long.toString(eventId), Long.toString(begin), Long.toString(alarmTime)
2656                     }), null);
2657             boolean found = false;
2658             try {
2659                 if (cursor != null && cursor.getCount() > 0) {
2660                     found = true;
2661                 }
2662             } finally {
2663                 if (cursor != null) {
2664                     cursor.close();
2665                 }
2666             }
2667             return found;
2668         }
2669     }
2670 
2671     protected interface ColorsColumns extends SyncStateContract.Columns {
2672 
2673         /**
2674          * The type of color, which describes how it should be used. Valid types
2675          * are {@link #TYPE_CALENDAR} and {@link #TYPE_EVENT}. Column name.
2676          * <P>
2677          * Type: INTEGER (NOT NULL)
2678          * </P>
2679          */
2680         public static final String COLOR_TYPE = "color_type";
2681 
2682         /**
2683          * This indicateds a color that can be used for calendars.
2684          */
2685         public static final int TYPE_CALENDAR = 0;
2686         /**
2687          * This indicates a color that can be used for events.
2688          */
2689         public static final int TYPE_EVENT = 1;
2690 
2691         /**
2692          * The key used to reference this color. This can be any non-empty
2693          * string, but must be unique for a given {@link #ACCOUNT_TYPE} and
2694          * {@link #ACCOUNT_NAME}. Column name.
2695          * <P>
2696          * Type: TEXT
2697          * </P>
2698          */
2699         public static final String COLOR_KEY = "color_index";
2700 
2701         /**
2702          * The color as an 8-bit ARGB integer value. Colors should specify alpha
2703          * as fully opaque (eg 0xFF993322) as the alpha may be ignored or
2704          * modified for display. It is reccomended that colors be usable with
2705          * light (near white) text. Apps should not depend on that assumption,
2706          * however. Column name.
2707          * <P>
2708          * Type: INTEGER (NOT NULL)
2709          * </P>
2710          */
2711         public static final String COLOR = "color";
2712 
2713     }
2714 
2715     /**
2716      * Fields for accessing colors available for a given account. Colors are
2717      * referenced by {@link #COLOR_KEY} which must be unique for a given
2718      * account name/type. These values can only be updated by the sync
2719      * adapter. Only {@link #COLOR} may be updated after the initial insert. In
2720      * addition, a row can only be deleted once all references to that color
2721      * have been removed from the {@link Calendars} or {@link Events} tables.
2722      */
2723     public static final class Colors implements ColorsColumns {
2724         /**
2725          * @hide
2726          */
2727         public static final String TABLE_NAME = "Colors";
2728         /**
2729          * The Uri for querying color information
2730          */
2731         @SuppressWarnings("hiding")
2732         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/colors");
2733 
2734         /**
2735          * This utility class cannot be instantiated
2736          */
Colors()2737         private Colors() {
2738         }
2739     }
2740 
2741     protected interface ExtendedPropertiesColumns {
2742         /**
2743          * The event the extended property belongs to. Column name.
2744          * <P>Type: INTEGER (foreign key to the Events table)</P>
2745          */
2746         public static final String EVENT_ID = "event_id";
2747 
2748         /**
2749          * The name of the extended property.  This is a uri of the form
2750          * {scheme}#{local-name} convention. Column name.
2751          * <P>Type: TEXT</P>
2752          */
2753         public static final String NAME = "name";
2754 
2755         /**
2756          * The value of the extended property. Column name.
2757          * <P>Type: TEXT</P>
2758          */
2759         public static final String VALUE = "value";
2760     }
2761 
2762     /**
2763      * Fields for accessing the Extended Properties. This is a generic set of
2764      * name/value pairs for use by sync adapters to add extra
2765      * information to events. There are three writable columns and all three
2766      * must be present when inserting a new value. They are:
2767      * <ul>
2768      * <li>{@link #EVENT_ID}</li>
2769      * <li>{@link #NAME}</li>
2770      * <li>{@link #VALUE}</li>
2771      * </ul>
2772      */
2773    public static final class ExtendedProperties implements BaseColumns,
2774             ExtendedPropertiesColumns, EventsColumns {
2775         public static final Uri CONTENT_URI =
2776                 Uri.parse("content://" + AUTHORITY + "/extendedproperties");
2777 
2778         /**
2779          * This utility class cannot be instantiated
2780          */
ExtendedProperties()2781         private ExtendedProperties() {}
2782 
2783         // TODO: fill out this class when we actually start utilizing extendedproperties
2784         // in the calendar application.
2785    }
2786 
2787     /**
2788      * A table provided for sync adapters to use for storing private sync state data.
2789      *
2790      * @see SyncStateContract
2791      */
2792     public static final class SyncState implements SyncStateContract.Columns {
2793         /**
2794          * This utility class cannot be instantiated
2795          */
SyncState()2796         private SyncState() {}
2797 
2798         private static final String CONTENT_DIRECTORY =
2799                 SyncStateContract.Constants.CONTENT_DIRECTORY;
2800 
2801         /**
2802          * The content:// style URI for this table
2803          */
2804         public static final Uri CONTENT_URI =
2805                 Uri.withAppendedPath(CalendarContract.CONTENT_URI, CONTENT_DIRECTORY);
2806     }
2807 
2808     /**
2809      * Columns from the EventsRawTimes table
2810      *
2811      * @hide
2812      */
2813     protected interface EventsRawTimesColumns {
2814         /**
2815          * The corresponding event id. Column name.
2816          * <P>Type: INTEGER (long)</P>
2817          */
2818         public static final String EVENT_ID = "event_id";
2819 
2820         /**
2821          * The RFC2445 compliant time the event starts. Column name.
2822          * <P>Type: TEXT</P>
2823          */
2824         public static final String DTSTART_2445 = "dtstart2445";
2825 
2826         /**
2827          * The RFC2445 compliant time the event ends. Column name.
2828          * <P>Type: TEXT</P>
2829          */
2830         public static final String DTEND_2445 = "dtend2445";
2831 
2832         /**
2833          * The RFC2445 compliant original instance time of the recurring event
2834          * for which this event is an exception. Column name.
2835          * <P>Type: TEXT</P>
2836          */
2837         public static final String ORIGINAL_INSTANCE_TIME_2445 = "originalInstanceTime2445";
2838 
2839         /**
2840          * The RFC2445 compliant last date this event repeats on, or NULL if it
2841          * never ends. Column name.
2842          * <P>Type: TEXT</P>
2843          */
2844         public static final String LAST_DATE_2445 = "lastDate2445";
2845     }
2846 
2847     /**
2848      * @hide
2849      */
2850     public static final class EventsRawTimes implements BaseColumns, EventsRawTimesColumns {
2851 
2852         /**
2853          * This utility class cannot be instantiated
2854          */
EventsRawTimes()2855         private EventsRawTimes() {}
2856     }
2857 }
2858