summaryrefslogtreecommitdiffstats
path: root/core/java/android/provider/Calendar.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/provider/Calendar.java')
-rw-r--r--core/java/android/provider/Calendar.java740
1 files changed, 453 insertions, 287 deletions
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..9a09805 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -16,34 +16,27 @@
package android.provider;
+import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.CursorEntityIterator;
+import android.content.Entity;
+import android.content.EntityIterator;
import android.content.Intent;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.net.Uri;
+import android.os.RemoteException;
import android.pim.ICalendar;
-import android.pim.RecurrenceSet;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.Log;
-import android.accounts.Account;
-import com.android.internal.database.ArrayListCursor;
-import com.google.android.gdata.client.AndroidGDataClient;
-import com.google.android.gdata.client.AndroidXmlParserFactory;
-import com.google.wireless.gdata.calendar.client.CalendarClient;
-import com.google.wireless.gdata.calendar.data.EventEntry;
-import com.google.wireless.gdata.calendar.data.Who;
-import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import com.google.wireless.gdata.data.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Vector;
/**
* The Calendar provider contains all calendar events.
@@ -57,8 +50,7 @@ public final class Calendar {
/**
* Broadcast Action: An event reminder.
*/
- public static final String
- EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
+ public static final String EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
/**
* These are the symbolic names for the keys used in the extra data
@@ -67,7 +59,7 @@ public final class Calendar {
public static final String EVENT_BEGIN_TIME = "beginTime";
public static final String EVENT_END_TIME = "endTime";
- public static final String AUTHORITY = "calendar";
+ public static final String AUTHORITY = "com.android.calendar";
/**
* The content:// style URL for the top-level calendar authority
@@ -76,16 +68,20 @@ public final class Calendar {
Uri.parse("content://" + AUTHORITY);
/**
+ * An optional insert, update or delete URI parameter that allows the caller
+ * to specify that it is a sync adapter. The default value is false. If true
+ * the dirty flag is not automatically set and the "syncToNetwork" parameter
+ * is set to false when calling
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ */
+ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+ /**
* Columns from the Calendars table that other tables join into themselves.
*/
public interface CalendarsColumns
{
/**
- * A string that uniquely identifies this contact to its source
- */
- public static final String SOURCE_ID = "sourceid";
-
- /**
* The color of the calendar
* <P>Type: INTEGER (color value)</P>
*/
@@ -137,13 +133,81 @@ public final class Calendar {
* <p>Type: String (blob)</p>
*/
public static final String SYNC_STATE = "sync_state";
+
+ /**
+ * The account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT = "_sync_account";
+
+ /**
+ * The type of the account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+ /**
+ * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ID = "_sync_id";
+
+ /**
+ * The last time, from the sync source's point of view, that this row has been synchronized.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_TIME = "_sync_time";
+
+ /**
+ * The version of the row, as assigned by the server.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_VERSION = "_sync_version";
+
+ /**
+ * For use by sync adapter at its discretion; not modified by CalendarProvider
+ * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
+ * schema change.
+ * TODO Replace this with something more general in the future.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DATA = "_sync_local_id";
+
+ /**
+ * Used only in persistent providers, and only during merging.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_MARK = "_sync_mark";
+
+ /**
+ * Used to indicate that local, unsynced, changes are present.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DIRTY = "_sync_dirty";
+
+ /**
+ * The name of the account instance to which this row belongs, which when paired with
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_NAME = "account_name";
+
+ /**
+ * The type of account to which this row belongs, which when paired with
+ * {@link #ACCOUNT_NAME} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_TYPE = "account_type";
}
/**
* Contains a list of available calendars.
*/
- public static class Calendars implements BaseColumns, SyncConstValue, CalendarsColumns
+ public static class Calendars implements BaseColumns, CalendarsColumns
{
+ private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?"
+ + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
+
public static final Cursor query(ContentResolver cr, String[] projection,
String where, String orderBy)
{
@@ -173,16 +237,14 @@ public final class Calendar {
public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
// delete all calendars that match this account
return Calendar.Calendars.delete(cr,
- Calendar.Calendars._SYNC_ACCOUNT + "=? AND "
- + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=?",
- new String[] {account.name, account.type});
+ WHERE_DELETE_FOR_ACCOUNT,
+ new String[] { account.name, account.type });
}
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/calendars");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
/**
* The default sort order for this table
@@ -225,6 +287,13 @@ public final class Calendar {
* <P>Type: String</P>
*/
public static final String OWNER_ACCOUNT = "ownerAccount";
+
+ /**
+ * Can the organizer respond to the event? If no, the status of the
+ * organizer should not be shown by the UI. Defaults to 1
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
}
public interface AttendeesColumns {
@@ -282,10 +351,8 @@ public final class Calendar {
public static final int ATTENDEE_STATUS_TENTATIVE = 4;
}
- public static final class Attendees implements BaseColumns,
- AttendeesColumns, EventsColumns {
- public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/attendees");
+ public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
// TODO: fill out this class when we actually start utilizing attendees
// in the calendar application.
@@ -341,11 +408,17 @@ public final class Calendar {
* This field is copied here so that we can efficiently filter out
* events that are declined without having to look in the Attendees
* table.
- *
+ *
* <P>Type: INTEGER (int)</P>
*/
public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
-
+
+ /**
+ * This column is available for use by sync adapters
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_ADAPTER_DATA = "syncAdapterData";
+
/**
* The comments feed uri.
* <P>Type: TEXT</P>
@@ -514,13 +587,215 @@ public final class Calendar {
* <P>Type: String</P>
*/
public static final String OWNER_ACCOUNT = "ownerAccount";
+
+ /**
+ * Whether the row has been deleted. A deleted row should be ignored.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
+ }
+
+ /**
+ * Contains one entry per calendar event. Recurring events show up as a single entry.
+ */
+ public static final class EventsEntity implements BaseColumns, EventsColumns, CalendarsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/event_entities");
+
+ public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
+ return new EntityIteratorImpl(cursor, resolver);
+ }
+
+ public static EntityIterator newEntityIterator(Cursor cursor,
+ ContentProviderClient provider) {
+ return new EntityIteratorImpl(cursor, provider);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ private final ContentResolver mResolver;
+ private final ContentProviderClient mProvider;
+
+ private static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders.MINUTES,
+ Reminders.METHOD,
+ };
+ private static final int COLUMN_MINUTES = 0;
+ private static final int COLUMN_METHOD = 1;
+
+ private static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees.ATTENDEE_NAME,
+ Attendees.ATTENDEE_EMAIL,
+ Attendees.ATTENDEE_RELATIONSHIP,
+ Attendees.ATTENDEE_TYPE,
+ Attendees.ATTENDEE_STATUS,
+ };
+ private static final int COLUMN_ATTENDEE_NAME = 0;
+ private static final int COLUMN_ATTENDEE_EMAIL = 1;
+ private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
+ private static final int COLUMN_ATTENDEE_TYPE = 3;
+ private static final int COLUMN_ATTENDEE_STATUS = 4;
+ private static final String[] EXTENDED_PROJECTION = new String[] {
+ ExtendedProperties._ID,
+ ExtendedProperties.NAME,
+ ExtendedProperties.VALUE
+ };
+ private static final int COLUMN_ID = 0;
+ private static final int COLUMN_NAME = 1;
+ private static final int COLUMN_VALUE = 2;
+
+ private static final String WHERE_EVENT_ID = "event_id=?";
+
+ public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
+ super(cursor);
+ mResolver = resolver;
+ mProvider = null;
+ }
+
+ public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
+ super(cursor);
+ mResolver = null;
+ mProvider = provider;
+ }
+
+ @Override
+ public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+ // we expect the cursor is already at the row we need to read from
+ final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
+ ContentValues cv = new ContentValues();
+ cv.put(Events._ID, eventId);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HTML_URI);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, COMMENTS_URI);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBILITY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, TRANSPARENCY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ HAS_EXTENDED_PROPERTIES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_EVENT);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
+ ORIGINAL_INSTANCE_TIME);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ GUESTS_CAN_INVITE_OTHERS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.URL);
+
+ Entity entity = new Entity(cv);
+ Cursor subCursor;
+ if (mResolver != null) {
+ subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues reminderValues = new ContentValues();
+ reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
+ reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
+ entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues attendeeValues = new ContentValues();
+ attendeeValues.put(Attendees.ATTENDEE_NAME,
+ subCursor.getString(COLUMN_ATTENDEE_NAME));
+ attendeeValues.put(Attendees.ATTENDEE_EMAIL,
+ subCursor.getString(COLUMN_ATTENDEE_EMAIL));
+ attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
+ subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
+ attendeeValues.put(Attendees.ATTENDEE_TYPE,
+ subCursor.getInt(COLUMN_ATTENDEE_TYPE));
+ attendeeValues.put(Attendees.ATTENDEE_STATUS,
+ subCursor.getInt(COLUMN_ATTENDEE_STATUS));
+ entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues extendedValues = new ContentValues();
+ extendedValues.put(ExtendedProperties._ID,
+ subCursor.getString(COLUMN_ID));
+ extendedValues.put(ExtendedProperties.NAME,
+ subCursor.getString(COLUMN_NAME));
+ extendedValues.put(ExtendedProperties.VALUE,
+ subCursor.getString(COLUMN_VALUE));
+ entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ cursor.moveToNext();
+ return entity;
+ }
+ }
}
/**
* Contains one entry per calendar event. Recurring events show up as a single entry.
*/
- public static final class Events implements BaseColumns, SyncConstValue,
- EventsColumns, CalendarsColumns {
+ public static final class Events implements BaseColumns, EventsColumns, CalendarsColumns {
private static final String[] FETCH_ENTRY_COLUMNS =
new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
@@ -532,8 +807,6 @@ public final class Calendar {
AttendeesColumns.ATTENDEE_TYPE,
AttendeesColumns.ATTENDEE_STATUS };
- private static CalendarClient sCalendarClient = null;
-
public static final Cursor query(ContentResolver cr, String[] projection) {
return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
}
@@ -554,172 +827,14 @@ public final class Calendar {
return null;
}
- public static final Uri insertVEvent(ContentResolver cr,
- ICalendar.Component event, long calendarId, int status,
- ContentValues values) {
-
- // TODO: define VEVENT component names as constants in some
- // appropriate class (ICalendar.Component?).
-
- values.clear();
-
- // title
- String title = extractValue(event, "SUMMARY");
- if (TextUtils.isEmpty(title)) {
- if (Config.LOGD) {
- Log.d(TAG, "No SUMMARY provided for event. "
- + "Cannot import.");
- }
- return null;
- }
- values.put(TITLE, title);
-
- // status
- values.put(STATUS, status);
-
- // description
- String description = extractValue(event, "DESCRIPTION");
- if (!TextUtils.isEmpty(description)) {
- values.put(DESCRIPTION, description);
- }
-
- // where
- String where = extractValue(event, "LOCATION");
- if (!StringUtils.isEmpty(where)) {
- values.put(EVENT_LOCATION, where);
- }
-
- // Calendar ID
- values.put(CALENDAR_ID, calendarId);
-
- boolean timesSet = false;
-
- // TODO: deal with VALARMs
-
- // dtstart & dtend
- Time time = new Time(Time.TIMEZONE_UTC);
- String dtstart = null;
- String dtend = null;
- String duration = null;
- ICalendar.Property dtstartProp = event.getFirstProperty("DTSTART");
- // TODO: handle "floating" timezone (no timezone specified).
- if (dtstartProp != null) {
- dtstart = dtstartProp.getValue();
- if (!TextUtils.isEmpty(dtstart)) {
- ICalendar.Parameter tzidParam =
- dtstartProp.getFirstParameter("TZID");
- if (tzidParam != null && tzidParam.value != null) {
- time.clear(tzidParam.value);
- }
- try {
- time.parse(dtstart);
- } catch (Exception e) {
- if (Config.LOGD) {
- Log.d(TAG, "Cannot parse dtstart " + dtstart, e);
- }
- return null;
- }
- if (time.allDay) {
- values.put(ALL_DAY, 1);
- }
- values.put(DTSTART, time.toMillis(false /* use isDst */));
- values.put(EVENT_TIMEZONE, time.timezone);
- }
-
- ICalendar.Property dtendProp = event.getFirstProperty("DTEND");
- if (dtendProp != null) {
- dtend = dtendProp.getValue();
- if (!TextUtils.isEmpty(dtend)) {
- // TODO: make sure the timezones are the same for
- // start, end.
- try {
- time.parse(dtend);
- } catch (Exception e) {
- if (Config.LOGD) {
- Log.d(TAG, "Cannot parse dtend " + dtend, e);
- }
- return null;
- }
- values.put(DTEND, time.toMillis(false /* use isDst */));
- }
- } else {
- // look for a duration
- ICalendar.Property durationProp =
- event.getFirstProperty("DURATION");
- if (durationProp != null) {
- duration = durationProp.getValue();
- if (!TextUtils.isEmpty(duration)) {
- // TODO: check that it is valid?
- values.put(DURATION, duration);
- }
- }
- }
- }
- if (TextUtils.isEmpty(dtstart) ||
- (TextUtils.isEmpty(dtend) && TextUtils.isEmpty(duration))) {
- if (Config.LOGD) {
- Log.d(TAG, "No DTSTART or DTEND/DURATION defined.");
- }
- return null;
- }
-
- // rrule
- if (!RecurrenceSet.populateContentValues(event, values)) {
- return null;
- }
-
- return cr.insert(CONTENT_URI, values);
- }
-
- /**
- * Returns a singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- * @param cr The ContentResolver used to lookup the address of the calendar server in the
- * settings database.
- * @return The singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- */
- private static synchronized CalendarClient getCalendarClient(ContentResolver cr) {
- if (sCalendarClient == null) {
- sCalendarClient = new CalendarClient(
- new AndroidGDataClient(cr),
- new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
- }
- return sCalendarClient;
- }
-
- /**
- * Extracts the attendees information out of event and adds it to a new ArrayList of columns
- * within the supplied ArrayList of rows. These rows are expected to be used within an
- * {@link ArrayListCursor}.
- */
- private static final void extractAttendeesIntoArrayList(EventEntry event,
- ArrayList<ArrayList> rows) {
- Log.d(TAG, "EVENT: " + event.toString());
- Vector<Who> attendees = (Vector<Who>) event.getAttendees();
-
- int numAttendees = attendees == null ? 0 : attendees.size();
-
- for (int i = 0; i < numAttendees; ++i) {
- Who attendee = attendees.elementAt(i);
- ArrayList row = new ArrayList();
- row.add(attendee.getValue());
- row.add(attendee.getEmail());
- row.add(attendee.getRelationship());
- row.add(attendee.getType());
- row.add(attendee.getStatus());
- rows.add(row);
- }
- }
-
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/events");
+ Uri.parse("content://" + AUTHORITY + "/events");
public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://calendar/deleted_events");
+ Uri.parse("content://" + AUTHORITY + "/deleted_events");
/**
* The default sort order for this table
@@ -733,12 +848,14 @@ public final class Calendar {
*/
public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
+
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end) {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
- return cr.query(builder.build(), projection, Calendars.SELECTED + "=1",
+ return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
null, DEFAULT_SORT_ORDER);
}
@@ -748,9 +865,9 @@ public final class Calendar {
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
if (TextUtils.isEmpty(where)) {
- where = Calendars.SELECTED + "=1";
+ where = WHERE_CALENDARS_SELECTED;
} else {
- where = "(" + where + ") AND " + Calendars.SELECTED + "=1";
+ where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
}
return cr.query(builder.build(), projection, where,
null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
@@ -759,9 +876,10 @@ public final class Calendar {
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/when");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/when");
public static final Uri CONTENT_BY_DAY_URI =
- Uri.parse("content://calendar/instances/whenbyday");
+ Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
/**
* The default sort order for this table.
@@ -851,60 +969,42 @@ public final class Calendar {
public static final String MAX_INSTANCE = "maxInstance";
/**
- * The minimum Julian day in the BusyBits table.
+ * The minimum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MIN_BUSYBITS = "minBusyBits";
+ public static final String MIN_EVENTDAYS = "minEventDays";
/**
- * The maximum Julian day in the BusyBits table.
+ * The maximum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MAX_BUSYBITS = "maxBusyBits";
+ public static final String MAX_EVENTDAYS = "maxEventDays";
}
-
+
public static final class CalendarMetaData implements CalendarMetaDataColumns {
}
-
- public interface BusyBitsColumns {
- /**
- * The Julian day number.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String DAY = "day";
+ public interface EventDaysColumns {
/**
- * The 24 bits representing the 24 1-hour time slots in a day.
- * If an event in the Instances table overlaps part of a 1-hour
- * time slot then the corresponding bit is set. The first time slot
- * (12am to 1am) is bit 0. The last time slot (11pm to midnight)
- * is bit 23.
+ * The Julian starting day number.
* <P>Type: INTEGER (int)</P>
*/
- public static final String BUSYBITS = "busyBits";
+ public static final String STARTDAY = "startDay";
+ public static final String ENDDAY = "endDay";
- /**
- * The number of all-day events that occur on this day.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String ALL_DAY_COUNT = "allDayCount";
}
-
- public static final class BusyBits implements BusyBitsColumns {
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
- public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
-
- // The number of minutes represented by one busy bit
- public static final int MINUTES_PER_BUSY_INTERVAL = 60;
-
- // The number of intervals in a day
- public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
+ public static final class EventDays implements EventDaysColumns {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/groupbyday");
+
+ public static final String[] PROJECTION = { STARTDAY, ENDDAY };
+ public static final String SELECTION = "selected=1";
/**
- * Retrieves the busy bits for the Julian days starting at "startDay"
+ * Retrieves the days with events for the Julian days starting at "startDay"
* for "numDays".
- *
+ *
* @param cr the ContentResolver
* @param startDay the first Julian day in the range
* @param numDays the number of days to load (must be at least 1)
@@ -918,8 +1018,8 @@ public final class Calendar {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, startDay);
ContentUris.appendId(builder, endDay);
- return cr.query(builder.build(), PROJECTION, null /* selection */,
- null /* selection args */, DAY);
+ return cr.query(builder.build(), PROJECTION, SELECTION,
+ null /* selection args */, STARTDAY);
}
}
@@ -955,7 +1055,7 @@ public final class Calendar {
public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
public static final String TABLE_NAME = "Reminders";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/reminders");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
}
public interface CalendarAlertsColumns {
@@ -1025,22 +1125,38 @@ public final class Calendar {
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "alarmTime ASC,begin ASC,title ASC";
+ public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
}
public static final class CalendarAlerts implements BaseColumns,
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
+
public static final String TABLE_NAME = "CalendarAlerts";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
-
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/calendar_alerts");
+
+ private static final String WHERE_ALARM_EXISTS = EVENT_ID + "=?"
+ + " AND " + BEGIN + "=?"
+ + " AND " + ALARM_TIME + "=?";
+
+ private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?";
+ private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC";
+
+ private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + SCHEDULED
+ + " AND " + ALARM_TIME + "<?"
+ + " AND " + ALARM_TIME + ">?"
+ + " AND " + END + ">=?";
+
/**
* This URI is for grouping the query results by event_id and begin
* time. This will return one result per instance of an event. So
* events with multiple alarms will appear just once, but multiple
* instances of a repeating event will show up multiple times.
*/
- public static final Uri CONTENT_URI_BY_INSTANCE =
- Uri.parse("content://calendar/calendar_alerts/by_instance");
+ public static final Uri CONTENT_URI_BY_INSTANCE =
+ Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
+
+ private static final boolean DEBUG = true;
public static final Uri insert(ContentResolver cr, long eventId,
long begin, long end, long alarmTime, int minutes) {
@@ -1059,15 +1175,15 @@ public final class Calendar {
}
public static final Cursor query(ContentResolver cr, String[] projection,
- String selection, String[] selectionArgs) {
+ String selection, String[] selectionArgs, String sortOrder) {
return cr.query(CONTENT_URI, projection, selection, selectionArgs,
- DEFAULT_SORT_ORDER);
+ sortOrder);
}
-
+
/**
* Finds the next alarm after (or equal to) the given time and returns
* the time of that alarm or -1 if no such alarm exists.
- *
+ *
* @param cr the ContentResolver
* @param millis the time in UTC milliseconds
* @return the next alarm time greater than or equal to "millis", or -1
@@ -1078,7 +1194,12 @@ public final class Calendar {
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
String[] projection = new String[] { ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ Cursor cursor = query(cr, projection,
+ WHERE_FINDNEXTALARMTIME,
+ new String[] {
+ Long.toString(millis)
+ },
+ SORT_ORDER_ALARMTIME_ASC);
long alarmTime = -1;
try {
if (cursor != null && cursor.moveToFirst()) {
@@ -1091,13 +1212,13 @@ public final class Calendar {
}
return alarmTime;
}
-
+
/**
* Searches the CalendarAlerts table for alarms that should have fired
* but have not and then reschedules them. This method can be called
* at boot time to restore alarms that may have been lost due to a
* phone reboot.
- *
+ *
* @param cr the ContentResolver
* @param context the Context
* @param manager the AlarmManager
@@ -1107,53 +1228,72 @@ public final class Calendar {
// Get all the alerts that have been scheduled but have not fired
// and should have fired by now and are not too old.
long now = System.currentTimeMillis();
- long ancient = now - 24 * DateUtils.HOUR_IN_MILLIS;
- String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED
- + " AND " + CalendarAlerts.ALARM_TIME + "<" + now
- + " AND " + CalendarAlerts.ALARM_TIME + ">" + ancient
- + " AND " + CalendarAlerts.END + ">=" + now;
+ long ancient = now - DateUtils.DAY_IN_MILLIS;
String[] projection = new String[] {
- _ID,
- BEGIN,
- END,
ALARM_TIME,
};
- Cursor cursor = CalendarAlerts.query(cr, projection, selection, null);
+
+ // TODO: construct an explicit SQL query so that we can add
+ // "GROUPBY" instead of doing a sort and de-dup
+ Cursor cursor = CalendarAlerts.query(cr,
+ projection,
+ WHERE_RESCHEDULE_MISSED_ALARMS,
+ new String[] {
+ Long.toString(now),
+ Long.toString(ancient),
+ Long.toString(now)
+ },
+ SORT_ORDER_ALARMTIME_ASC);
if (cursor == null) {
return;
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+
+ if (DEBUG) {
Log.d(TAG, "missed alarms found: " + cursor.getCount());
}
-
+
try {
+ long alarmTime = -1;
+
while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- long begin = cursor.getLong(1);
- long end = cursor.getLong(2);
- long alarmTime = cursor.getLong(3);
- Uri uri = ContentUris.withAppendedId(CONTENT_URI, id);
- Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
- intent.setData(uri);
- intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, begin);
- intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end);
- PendingIntent sender = PendingIntent.getBroadcast(context,
- 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- Log.w(TAG, "rescheduling missed alarm, id: " + id + " begin: " + begin
- + " end: " + end + " alarmTime: " + alarmTime);
- manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
+ long newAlarmTime = cursor.getLong(0);
+ if (alarmTime != newAlarmTime) {
+ if (DEBUG) {
+ Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
+ }
+ scheduleAlarm(context, manager, newAlarmTime);
+ alarmTime = newAlarmTime;
+ }
}
} finally {
cursor.close();
}
-
}
-
+
+ public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
+ if (DEBUG) {
+ Time time = new Time();
+ time.set(alarmTime);
+ String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
+ Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
+ }
+
+ if (manager == null) {
+ manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ Intent intent = new Intent(EVENT_REMINDER_ACTION);
+ intent.setData(ContentUris.withAppendedId(Calendar.CONTENT_URI, alarmTime));
+ intent.putExtra(ALARM_TIME, alarmTime);
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+ manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
+ }
+
/**
* Searches for an entry in the CalendarAlerts table that matches
* the given event id, begin time and alarm time. If one is found
* then this alarm already exists and this method returns true.
- *
+ *
* @param cr the ContentResolver
* @param eventId the event id to match
* @param begin the start time of the event in UTC millis
@@ -1163,13 +1303,18 @@ public final class Calendar {
*/
public static final boolean alarmExists(ContentResolver cr, long eventId,
long begin, long alarmTime) {
- String selection = CalendarAlerts.EVENT_ID + "=" + eventId
- + " AND " + CalendarAlerts.BEGIN + "=" + begin
- + " AND " + CalendarAlerts.ALARM_TIME + "=" + alarmTime;
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
- String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ String[] projection = new String[] { ALARM_TIME };
+ Cursor cursor = query(cr,
+ projection,
+ WHERE_ALARM_EXISTS,
+ new String[] {
+ Long.toString(eventId),
+ Long.toString(begin),
+ Long.toString(alarmTime)
+ },
+ null);
boolean found = false;
try {
if (cursor != null && cursor.getCount() > 0) {
@@ -1208,9 +1353,30 @@ public final class Calendar {
public static final class ExtendedProperties implements BaseColumns,
ExtendedPropertiesColumns, EventsColumns {
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/extendedproperties");
+ Uri.parse("content://" + AUTHORITY + "/extendedproperties");
// TODO: fill out this class when we actually start utilizing extendedproperties
// in the calendar application.
}
+
+ /**
+ * A table provided for sync adapters to use for storing private sync state data.
+ *
+ * @see SyncStateContract
+ */
+ public static final class SyncState implements SyncStateContract.Columns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private SyncState() {}
+
+ public static final String CONTENT_DIRECTORY =
+ SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
+ }
}